From 4a4009133cc8ebf97e18e9bb39be60ec2cd7a7c8 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 25 Aug 2026 16:33:36 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E4=BA=86=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E5=B0=8F=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/css/routes/orders.css | 8 +- assets/js/order-center.js | 107 +++++++++++++++--- assets/js/routes/user-center.js | 19 ++-- .../user-center-renderers/profile-orders.php | 46 +++----- .../post-visual-contract.test.cjs | 13 ++- 5 files changed, 130 insertions(+), 63 deletions(-) diff --git a/assets/css/routes/orders.css b/assets/css/routes/orders.css index 0c9784ec..88bc0238 100644 --- a/assets/css/routes/orders.css +++ b/assets/css/routes/orders.css @@ -58,10 +58,10 @@ .aksrt-orders-page__filter-group { display: flex; flex: 1 1 100%; gap: 10px; align-items: center; } .aksrt-orders-page__filter-label { flex: 0 0 58px; color: var(--orders-muted); font-size: 12px; font-weight: 650; } .aksrt-orders-page__status-filters, .aksrt-orders-page__type-filters { display: flex; flex-wrap: wrap; gap: 6px; } -.aksrt-orders-page__filter-chip { display: inline-flex; gap: 5px; align-items: center; padding: 5px 9px; border: 1px solid var(--orders-line); border-radius: 999px; background: var(--surface-1, #fff); color: var(--orders-muted); font-size: 12px; text-decoration: none; } +.aksrt-orders-page__filter-chip { display: inline-flex; gap: 5px; align-items: center; padding: 5px 9px; border: 1px solid var(--orders-line); border-radius: 999px; background: var(--surface-1, #fff); color: var(--orders-muted); font: inherit; font-size: 12px; text-decoration: none; cursor: pointer; } .aksrt-orders-page__filter-chip.is-active { border-color: var(--color-primary, #3b82f6); background: color-mix(in srgb, var(--color-primary, #3b82f6) 10%, var(--surface-1, #fff)); color: var(--color-primary, #3b82f6); font-weight: 700; } .aksrt-orders-page__filter-count { display: inline-grid; min-width: 17px; height: 17px; padding: 0 4px; place-items: center; border-radius: 999px; background: var(--color-warning, #c87819); color: #fff; font-size: 10px; line-height: 1; } -.aksrt-orders-page__clear { margin-left: auto; color: var(--color-primary, #3b82f6); font-size: 12px; text-decoration: none; } +.aksrt-orders-page__clear { margin-left: auto; padding: 0; border: 0; background: transparent; color: var(--color-primary, #3b82f6); font: inherit; font-size: 12px; text-decoration: none; cursor: pointer; } .aksrt-orders-page__list { display: grid; gap: 10px; } .aksrt-orders-page__card { overflow: hidden; border: 1px solid var(--orders-line); border-radius: 13px; background: var(--surface-1, #fff); transition: border-color .18s ease, transform .18s ease, box-shadow .18s ease; } @@ -84,8 +84,10 @@ .aksrt-orders-page__countdown { margin-right: auto; color: var(--color-warning, #c87819); font-size: 11px; font-variant-numeric: tabular-nums; } .aksrt-orders-page__pagination { display: flex; justify-content: center; gap: 6px; margin-top: 20px; } -.aksrt-orders-page__page-chip { display: grid; width: 31px; height: 31px; place-items: center; border: 1px solid var(--orders-line); border-radius: 8px; color: var(--orders-muted); font-size: 12px; text-decoration: none; } +.aksrt-orders-page__page-chip { display: grid; width: 31px; height: 31px; padding: 0; place-items: center; border: 1px solid var(--orders-line); border-radius: 8px; background: var(--surface-1, #fff); color: var(--orders-muted); font: inherit; font-size: 12px; text-decoration: none; cursor: pointer; } .aksrt-orders-page__page-chip.is-active { border-color: var(--color-primary, #3b82f6); background: var(--color-primary, #3b82f6); color: #fff; } +.aksrt-orders-page [data-aksrt-order-list], .aksrt-orders-page__stats, .aksrt-orders-page__pagination { transition: opacity .16s ease; } +.aksrt-orders-page.is-loading [data-aksrt-order-list], .aksrt-orders-page.is-loading .aksrt-orders-page__stats, .aksrt-orders-page.is-loading .aksrt-orders-page__pagination { opacity: .55; pointer-events: none; } .aksrt-orders-page__empty { padding: 52px 20px; border: 1px dashed var(--orders-line); border-radius: 13px; background: var(--surface-2, #f7f8fb); text-align: center; } .aksrt-orders-page__empty i { display: block; margin-bottom: 12px; color: var(--color-primary, #3b82f6); font-size: 30px; } .aksrt-orders-page__empty h3 { margin: 0 0 7px; font-size: 15px; }.aksrt-orders-page__empty p { margin: 0 0 16px; color: var(--orders-muted); font-size: 13px; } diff --git a/assets/js/order-center.js b/assets/js/order-center.js index e23c380c..9084a5d6 100644 --- a/assets/js/order-center.js +++ b/assets/js/order-center.js @@ -160,11 +160,12 @@ }); } - function currentViewPath() { - var params = new URLSearchParams(window.location.search); - var allowed = ["order_status", "order_type", "order_number", "order_page"]; + function currentViewPath(state) { var query = new URLSearchParams(); - allowed.forEach(function (key) { if (params.get(key)) query.set(key, params.get(key)); }); + if (state.query.status && state.query.status !== "all") query.set("order_status", state.query.status); + if (state.query.type) query.set("order_type", state.query.type); + if (state.query.orderNumber) query.set("order_number", state.query.orderNumber); + if (Number(state.query.page) > 1) query.set("order_page", String(state.query.page)); return "/orders/view" + (query.toString() ? "?" + query.toString() : ""); } @@ -195,6 +196,11 @@ root.innerHTML = '
全部' + Number(stats.total || 0) + "
" + '
待支付' + Number(stats.pending || 0) + "
" + '
已支付' + Number(stats.paid || 0) + "
"; + var pendingCount = state.root.querySelector('[data-aksrt-order-filter="status"][data-aksrt-order-filter-value="pending"] .aksrt-orders-page__filter-count'); + if (pendingCount) { + pendingCount.textContent = String(Number(stats.pending || 0)); + pendingCount.hidden = Number(stats.pending || 0) < 1; + } } function renderPagination(state, view) { @@ -204,14 +210,14 @@ var root = document.createElement("nav"); root.className = "aksrt-orders-page__pagination"; root.setAttribute("aria-label", "订单分页"); - var base = new URL(window.location.href); for (var i = 1; i <= Number(view.total_pages); i++) { - base.searchParams.set("order_page", String(i)); - var link = document.createElement("a"); - link.className = "aksrt-orders-page__page-chip" + (i === Number(view.page) ? " is-active" : ""); - link.href = base.toString(); - link.textContent = String(i); - root.appendChild(link); + var button = document.createElement("button"); + button.type = "button"; + button.className = "aksrt-orders-page__page-chip" + (i === Number(view.page) ? " is-active" : ""); + button.setAttribute("data-aksrt-order-page", String(i)); + if (i === Number(view.page)) button.setAttribute("aria-current", "page"); + button.textContent = String(i); + root.appendChild(button); } var list = state.root.querySelector("[data-aksrt-order-list]"); if (list) list.after(root); @@ -226,32 +232,67 @@ } function renderView(state, view) { + var viewQuery = view.query || {}; + state.query.status = String(viewQuery.order_status || "all"); + state.query.type = String(viewQuery.order_type || ""); + state.query.orderNumber = String(viewQuery.order_number || ""); + state.query.page = Math.max(1, Number(view.page || viewQuery.order_page || 1)); renderStats(state, view.stats || {}); var list = state.root.querySelector("[data-aksrt-order-list]"); if (!list) return; var items = Array.isArray(view.items) ? view.items : []; if (!items.length) { - list.innerHTML = '

没有匹配的订单

换个条件试试,或清除当前筛选。

'; - list.classList.add("is-empty"); + list.innerHTML = '

没有匹配的订单

换个条件试试,或清除当前筛选。

'; + list.classList.remove("aksrt-orders-page__list"); + list.classList.add("aksrt-orders-page__empty"); } else { - list.classList.remove("is-empty"); + list.classList.remove("aksrt-orders-page__empty"); + list.classList.add("aksrt-orders-page__list"); list.innerHTML = items.map(cardHtml).join(""); } renderPagination(state, view); + syncControls(state); updateCountdowns(state); } - function refreshView(state) { - return request(currentViewPath(), undefined, "GET").then(function (result) { + function syncControls(state) { + state.root.querySelectorAll("[data-aksrt-order-filter]").forEach(function (button) { + var field = button.getAttribute("data-aksrt-order-filter"); + var expected = field === "status" ? state.query.status : state.query.type; + var active = button.getAttribute("data-aksrt-order-filter-value") === expected; + button.classList.toggle("is-active", active); + button.setAttribute("aria-pressed", active ? "true" : "false"); + }); + var input = state.root.querySelector("#aksrt-order-number"); + if (input && input.value !== state.query.orderNumber) input.value = state.query.orderNumber; + } + + function setViewLoading(state, loading) { + state.root.classList.toggle("is-loading", loading); + state.root.setAttribute("aria-busy", loading ? "true" : "false"); + } + + function loadView(state, patch) { + Object.keys(patch || {}).forEach(function (key) { state.query[key] = patch[key]; }); + var requestId = ++state.viewRequest; + setViewLoading(state, true); + return request(currentViewPath(state), undefined, "GET").then(function (result) { + if (requestId !== state.viewRequest) return null; if (!result || !result.data) throw new Error("刷新失败"); renderView(state, result.data); return result.data; }).catch(function (error) { - notyf(errorMessage(error, "订单状态已变更,请刷新后查看"), "danger"); + if (requestId === state.viewRequest) notyf(errorMessage(error, "订单加载失败,请稍后重试"), "danger"); return null; + }).finally(function () { + if (requestId === state.viewRequest) setViewLoading(state, false); }); } + function refreshView(state) { + return loadView(state); + } + function startPayment(state, button) { var number = button.getAttribute("data-aksrt-order-pay") || ""; var method = button.getAttribute("data-aksrt-order-method") || ""; @@ -316,6 +357,8 @@ detailReplacementSession: null, countdownTimer: null, operationKeys: {}, + query: { status: "all", type: "", orderNumber: "", page: 1 }, + viewRequest: 0, }; state.onRootClick = function (event) { @@ -327,6 +370,27 @@ if (panel) panel.hidden = expanded; return; } + var filter = closest(event.target, "[data-aksrt-order-filter]"); + if (filter && root.contains(filter)) { + event.preventDefault(); + var field = filter.getAttribute("data-aksrt-order-filter"); + var patch = { page: 1 }; + patch[field] = filter.getAttribute("data-aksrt-order-filter-value") || ""; + loadView(state, patch); + return; + } + var page = closest(event.target, "[data-aksrt-order-page]"); + if (page && root.contains(page)) { + event.preventDefault(); + loadView(state, { page: Math.max(1, Number(page.getAttribute("data-aksrt-order-page") || 1)) }); + return; + } + var clear = closest(event.target, "[data-aksrt-orders-clear]"); + if (clear && root.contains(clear)) { + event.preventDefault(); + loadView(state, { status: "all", type: "", orderNumber: "", page: 1 }); + return; + } var detail = closest(event.target, "[data-aksrt-order-detail]"); if (detail && root.contains(detail)) { openDetail(state, detail.getAttribute("data-aksrt-order-detail")); return; } var pay = closest(event.target, "[data-aksrt-order-pay]"); @@ -334,6 +398,13 @@ var cancel = closest(event.target, "[data-aksrt-order-cancel]"); if (cancel && root.contains(cancel)) { event.preventDefault(); closeOrder(state, cancel); } }; + state.onRootSubmit = function (event) { + var form = closest(event.target, "[data-aksrt-orders-search]"); + if (!form || !root.contains(form)) return; + event.preventDefault(); + var input = form.querySelector("#aksrt-order-number"); + loadView(state, { orderNumber: input ? String(input.value || "").trim() : "", page: 1 }); + }; state.onModalClose = function (event) { var detail = event && event.detail ? event.detail : {}; if (!state.detailSession || detail.session !== state.detailSession) return; @@ -365,6 +436,7 @@ }; root.addEventListener("click", state.onRootClick); + root.addEventListener("submit", state.onRootSubmit); document.addEventListener("aksrt:modal:close", state.onModalClose); document.addEventListener("click", state.onDocumentClick); state.countdownTimer = window.setInterval(function () { updateCountdowns(state); }, 1000); @@ -389,6 +461,7 @@ if (!state) return; if (activeDetail(state, state.detailOrderNumber)) window.AksrtModalRuntime.close("replace"); item.removeEventListener("click", state.onRootClick); + item.removeEventListener("submit", state.onRootSubmit); document.removeEventListener("aksrt:modal:close", state.onModalClose); document.removeEventListener("click", state.onDocumentClick); window.clearInterval(state.countdownTimer); diff --git a/assets/js/routes/user-center.js b/assets/js/routes/user-center.js index ae1a7fcd..8305ce4c 100644 --- a/assets/js/routes/user-center.js +++ b/assets/js/routes/user-center.js @@ -82,12 +82,13 @@ } target.classList.add('is-loading'); var url = restUrl.replace(/\/$/, '') + '/user-center/panel/' + encodeURIComponent(name); - if (name === 'creator' || name === 'revenue' || name === 'posts' || name === 'follow' || name === 'orders') { + if (name === 'creator' || name === 'revenue' || name === 'posts' || name === 'follow') { var params = queryParams || new URLSearchParams(window.location.search); - var allowedParams = name === 'creator' ? ['income_page'] : - (name === 'revenue' ? ['reward_tab', 'commission_page', 'user_page'] : - (name === 'posts' ? ['post_status', 'posts_page'] : - (name === 'follow' ? ['follow_list', 'follow_page'] : ['order_status', 'order_type', 'order_number', 'order_page']))); + var allowedParams = []; + if (name === 'creator') allowedParams = ['income_page']; + if (name === 'revenue') allowedParams = ['reward_tab', 'commission_page', 'user_page']; + if (name === 'posts') allowedParams = ['post_status', 'posts_page']; + if (name === 'follow') allowedParams = ['follow_list', 'follow_page']; allowedParams.forEach(function(key) { if (params.get(key)) url += (url.indexOf('?') === -1 ? '?' : '&') + key + '=' + encodeURIComponent(params.get(key)); }); @@ -174,7 +175,7 @@ if (!window.history.replaceState) return; var pathTab = name === 'credits' ? 'balance' : name; var u = new URL('/user/' + pathTab + '/', window.location.origin); - if (sourceUrl && name !== 'revenue') { + if (sourceUrl && name !== 'revenue' && name !== 'orders') { var source = new URL(sourceUrl, window.location.href); source.searchParams.forEach(function(value, key) { u.searchParams.set(key, value); }); } @@ -256,7 +257,11 @@ }); if (drawerTrigger) drawerTrigger.addEventListener('click', openDrawer); if (drawerOverlay) drawerOverlay.addEventListener('click', closeDrawer); - activate(tabFromUrl(), false); + var initialTab = tabFromUrl(); + if (initialTab === 'orders' && window.location.search && window.history.replaceState) { + window.history.replaceState({}, '', window.location.pathname + window.location.hash); + } + activate(initialTab, false); } /* ==================== 个人信息保存 ==================== */ diff --git a/inc/helpers/user-center-renderers/profile-orders.php b/inc/helpers/user-center-renderers/profile-orders.php index b584705c..2c421c32 100644 --- a/inc/helpers/user-center-renderers/profile-orders.php +++ b/inc/helpers/user-center-renderers/profile-orders.php @@ -15,10 +15,10 @@ if ($user_id <= 0) { } $raw_query = [ - 'status' => $_GET['order_status'] ?? 'all', - 'type' => $_GET['order_type'] ?? '', - 'order_number' => $_GET['order_number'] ?? '', - 'page' => $_GET['order_page'] ?? 1, + 'status' => 'all', + 'type' => '', + 'order_number' => '', + 'page' => 1, 'per_page' => 20, ]; $query = \AKSRT\Payment\OrderCenterQuery::from_array($raw_query); @@ -35,23 +35,6 @@ $status = $query->status(); $type = $query->product_type(); $order_number = $query->order_number(); -$orders_url = static function (array $override = []) use ($status, $type, $order_number, $page): string { - $values = array_merge([ - 'order_status' => $status, - 'order_type' => $type, - 'order_number' => $order_number, - 'order_page' => $page, - ], $override); - $base = function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url('orders') : home_url('/profile/'); - $values['tab'] = 'orders'; - foreach ($values as $key => $value) { - if ($value === '' || $value === null || ($key === 'order_page' && (int) $value <= 1) || ($key === 'order_status' && $value === 'all')) { - unset($values[$key]); - } - } - return add_query_arg($values, remove_query_arg(['order_tab', 'otab', 'section'], $base)); -}; - $status_options = [ 'all' => __('全部', 'aksrt'), 'pending' => __('待支付', 'aksrt'), @@ -78,13 +61,10 @@ $status_options = [ - @@ -94,10 +74,10 @@ $status_options = [ @@ -105,11 +85,11 @@ $status_options = [
$label) : ?> - +
- + @@ -144,9 +124,9 @@ $status_options = [ - 1) : ?> + 1) : ?> -

+

diff --git a/tests/widget-system/post-visual-contract.test.cjs b/tests/widget-system/post-visual-contract.test.cjs index f99111db..0aa9a6fb 100644 --- a/tests/widget-system/post-visual-contract.test.cjs +++ b/tests/widget-system/post-visual-contract.test.cjs @@ -367,8 +367,11 @@ test('profile routes load versioned leaf styles and keep dialogs hidden initiall assert.match(userCenterCreditsCss, /\.uc-asset-tabs,\s*\.uc-asset-tabs\.aksrt-panel\s*\{[^}]*background:\s*var\(--surface-1,\s*#fff\);[^}]*border:[^;}]+;[^}]*box-shadow:[^;}]+;/); assert.doesNotMatch(profileCredits, /credits-signin\.php|data-aksrt-signin-calendar/); assert.match(profileOrders, /data-aksrt-orders-filter-panel hidden/); - assert.match(profileOrders, /'status'\s*=>\s*\$_GET\['order_status'\]\s*\?\?\s*'all'/); + assert.doesNotMatch(profileOrders, /\$_GET|add_query_arg|name="order_(?:status|type|number|page)"/); assert.match(profileOrders, /data-aksrt-orders-filter-panel hidden[\s\S]*?aksrt-orders-page__status-filters[\s\S]*?aksrt-orders-page__type-filters/); + assert.match(profileOrders, /data-aksrt-order-filter="status"[\s\S]*?data-aksrt-order-filter="type"/); + assert.match(profileOrders, /data-aksrt-order-page=/); + assert.doesNotMatch(profileOrders, /aksrt-orders-page__(?:filter-chip|page-chip|clear)[^>]*href=/); assert.doesNotMatch(profileOrders, /aksrt-orders-page__tabs|aksrt-orders-page__tab(?:\s|<|"|')/); assert.match(ordersCss, /\.aksrt-orders-page__filter-panel\[hidden\]\s*\{\s*display:\s*none\s*!important;/); assert.match(ordersCss, /\.aksrt-orders-page__status-filters, \.aksrt-orders-page__type-filters/); @@ -377,8 +380,12 @@ test('profile routes load versioned leaf styles and keep dialogs hidden initiall assert.doesNotMatch(profileOrders, /can_open|entitlement_label|is-entitlement/); assert.doesNotMatch(orderCenterJs, /can_open|entitlement_label|is-entitlement/); assert.doesNotMatch(ordersCss, /is-entitlement/); - assert.match(userCenterJs, /name === 'follow' \|\| name === 'orders'/); - assert.match(userCenterJs, /\['order_status', 'order_type', 'order_number', 'order_page'\]/); + assert.match(orderCenterJs, /function loadView\(state, patch\)/); + assert.match(orderCenterJs, /data-aksrt-order-filter/); + assert.match(orderCenterJs, /data-aksrt-order-page/); + assert.doesNotMatch(orderCenterJs, /new URL\(window\.location\.href\)/); + assert.doesNotMatch(userCenterJs, /\['order_status', 'order_type', 'order_number', 'order_page'\]/); + assert.match(userCenterJs, /initialTab === 'orders'[\s\S]*?window\.history\.replaceState/); }); test('public user home stays param-free and swaps panels via REST fragments', () => {