diff --git a/assets/css/routes/commerce.css b/assets/css/routes/commerce.css index 9b86cda9..8b9ca04f 100644 --- a/assets/css/routes/commerce.css +++ b/assets/css/routes/commerce.css @@ -1,5 +1,5 @@ /** - * Commerce center shared shell (cart / orders / revenue). + * Commerce center shared shell (cart / revenue). * * @package AKSRT */ @@ -32,8 +32,8 @@ display: inline-flex; align-items: center; padding: 6px 12px; - border-radius: 999px; border: 1px solid var(--color-line, rgba(0, 0, 0, 0.08)); + border-radius: 999px; background: var(--surface-1, #fff); color: var(--color-ink-2, #4b5563); font-size: 13px; @@ -42,14 +42,14 @@ } .aksrt-commerce-header__nav-item:hover { - color: var(--color-primary, #f04494); border-color: color-mix(in srgb, var(--color-primary, #f04494) 45%, transparent); + color: var(--color-primary, #f04494); } .aksrt-commerce-header__nav-item.is-active { - color: var(--color-on-primary, #fff); - background: var(--color-primary, #f04494); border-color: transparent; + background: var(--color-primary, #f04494); + color: var(--color-on-primary, #fff); } /* Soften revenue dashboard cards — task-first, not ops console */ @@ -60,18 +60,18 @@ .aksrt-revenue-page__stat-card { padding: 14px 16px !important; - border-radius: 12px !important; - box-shadow: none !important; border: 1px solid var(--color-line-soft, rgba(0, 0, 0, 0.06)) !important; + border-radius: 12px !important; background: var(--surface-1, #fff) !important; + box-shadow: none !important; } .aksrt-revenue-page__stat-icon { width: 36px !important; height: 36px !important; - font-size: 15px !important; background: var(--body-bg-color, #f5f6f7) !important; color: var(--color-ink-2, #4b5563) !important; + font-size: 15px !important; } .aksrt-revenue-page__stat-value { @@ -83,98 +83,6 @@ font-size: 12px !important; } -/* Orders / cart title rhythm when using shared header */ -.aksrt-orders-page__header, .aksrt-cart-page__header > div:first-child { margin-bottom: 0; } - -.aksrt-orders-page__tabs { - margin-top: 4px; -} - - -/* After-sale multi-step + coupon wallet */ -.aksrt-orders-page__extra-nav { - display: flex; - flex-wrap: wrap; - gap: 8px; - margin: 0 0 12px; -} -.aksrt-orders-page__extra-link { - padding: 6px 12px; - border-radius: 999px; - background: var(--surface-2, #f3f4f6); - color: inherit; - text-decoration: none; - font-size: 13px; -} -.aksrt-orders-page__extra-link.is-active { - background: var(--focus-color, var(--color-primary)); - color: #fff; -} -.aksrt-as-list { display: grid; gap: 12px; } -.aksrt-as-card { - border: 1px solid var(--main-border-color, #eee); - border-radius: 10px; - padding: 14px; - background: var(--main-bg-color, #fff); -} -.aksrt-as-card__head { - display: flex; - flex-wrap: wrap; - gap: 8px; - align-items: center; - margin-bottom: 6px; -} -.aksrt-as-card__status { - margin-left: auto; - color: var(--focus-color, #2f6df6); - font-weight: 600; -} -.aksrt-as-timeline { - margin: 10px 0 0; - padding-left: 18px; - color: var(--muted-2-color, #888); - font-size: 12px; -} -.aksrt-as-card__actions { - display: flex; - gap: 8px; - margin-top: 10px; -} -.aksrt-coupon-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); - gap: 12px; -} -.aksrt-coupon-card { - border: 1px dashed var(--focus-color, #2f6df6); - border-radius: 10px; - padding: 12px; - background: color-mix(in srgb, var(--focus-color, #2f6df6) 6%, #fff); -} -.aksrt-coupon-card__value { - font-size: 20px; - font-weight: 700; - color: var(--focus-color, #2f6df6); -} -.aksrt-coupon-card__code { - font-family: ui-monospace, monospace; - margin: 6px 0; -} -.aksrt-coupon-card__meta { - font-size: 12px; - color: var(--muted-2-color, #888); - margin-bottom: 4px; -} -.aksrt-orders-page__field { margin-bottom: 12px; } -.aksrt-orders-page__input, -.aksrt-orders-page__select, -.aksrt-orders-page__textarea { - width: 100%; - box-sizing: border-box; - padding: 8px 10px; - border: 1px solid var(--main-border-color, #ddd); - border-radius: 6px; -} diff --git a/assets/css/routes/orders.css b/assets/css/routes/orders.css index 96b8e09c..c5146549 100644 --- a/assets/css/routes/orders.css +++ b/assets/css/routes/orders.css @@ -47,10 +47,10 @@ .aksrt-orders-page__search { display: flex; gap: 8px; align-items: center; margin: 20px 0 12px; padding: 7px; border: 1px solid var(--orders-line); border-radius: 12px; background: var(--surface-1, #fff); } .aksrt-orders-page__search > i { margin-left: 7px; color: var(--orders-muted); font-size: 13px; } .aksrt-orders-page__search input { flex: 1; min-width: 0; border: 0; outline: 0; background: transparent; color: var(--orders-ink); font-size: 13px; } -.aksrt-orders-page__search button, .aksrt-orders-page__card-actions .btn-primary, .aksrt-orders-page__card-actions .btn-outline, .aksrt-orders-page__card-actions .btn-ghost, .aksrt-orders-page__empty .btn-outline, .aksrt-modal__footer .btn-primary, .aksrt-modal__footer .btn-outline, .aksrt-modal__footer .btn-ghost { display: inline-flex; align-items: center; justify-content: center; min-height: 34px; padding: 0 13px; border-radius: 8px; border: 1px solid transparent; font-size: 12px; font-weight: 650; text-decoration: none; cursor: pointer; } -.aksrt-orders-page .btn-primary, .aksrt-modal__footer .btn-primary { background: var(--color-primary, #3b82f6); color: var(--color-on-primary, #fff); } -.aksrt-orders-page .btn-outline, .aksrt-modal__footer .btn-outline { border-color: var(--orders-line); background: var(--surface-1, #fff); color: var(--orders-ink); } -.aksrt-orders-page .btn-ghost, .aksrt-modal__footer .btn-ghost { background: transparent; color: var(--orders-muted); } +.aksrt-orders-page__search button, .aksrt-orders-page__card-actions .btn-primary, .aksrt-orders-page__card-actions .btn-outline, .aksrt-orders-page__card-actions .btn-ghost, .aksrt-orders-page__empty .btn-outline, .aksrt-order-detail__actions .btn-primary, .aksrt-order-detail__actions .btn-outline, .aksrt-order-detail__actions .btn-ghost { display: inline-flex; align-items: center; justify-content: center; min-height: 34px; padding: 0 13px; border-radius: 8px; border: 1px solid transparent; font-size: 12px; font-weight: 650; text-decoration: none; cursor: pointer; } +.aksrt-orders-page .btn-primary, .aksrt-order-detail__actions .btn-primary { background: var(--color-primary, #3b82f6); color: var(--color-on-primary, #fff); } +.aksrt-orders-page .btn-outline, .aksrt-order-detail__actions .btn-outline { border-color: var(--orders-line); background: var(--surface-1, #fff); color: var(--orders-ink); } +.aksrt-orders-page .btn-ghost, .aksrt-order-detail__actions .btn-ghost { background: transparent; color: var(--orders-muted); } .aksrt-orders-page__filter-toggle { background: var(--surface-2, #f4f6f9); color: var(--orders-ink); white-space: nowrap; } .aksrt-orders-page__filter-panel { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: -2px 0 14px; padding: 12px; border-radius: 10px; background: var(--surface-2, #f4f6f9); } @@ -60,7 +60,7 @@ .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__clear { margin-left: auto; color: var(--color-primary, #3b82f6); font-size: 12px; text-decoration: none; } -.aksrt-orders-page__tabs { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0; margin: 0 0 14px; border-bottom: 1px solid var(--orders-line); } +.aksrt-orders-page__tabs { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0; margin: 4px 0 14px; border-bottom: 1px solid var(--orders-line); } .aksrt-orders-page__tab { position: relative; padding: 11px 8px; color: var(--orders-muted); text-align: center; font-size: 13px; font-weight: 650; text-decoration: none; } .aksrt-orders-page__tab.is-active { color: var(--orders-ink); } .aksrt-orders-page__tab.is-active::after { position: absolute; right: 28%; bottom: -1px; left: 28%; height: 2px; border-radius: 2px; background: var(--color-primary, #3b82f6); content: ""; } @@ -94,9 +94,25 @@ .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; } -.aksrt-modal__footer { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 7px; padding: 12px 16px; border-top: 1px solid var(--orders-line); background: var(--surface-2, #f7f8fb); } +.aksrt-order-detail__actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 7px; padding: 12px 16px; border-top: 1px solid var(--orders-line); background: var(--surface-2, #f7f8fb); } .aksrt-order-detail__hero { display: flex; align-items: center; justify-content: space-between; padding: 15px; border-radius: 11px; background: color-mix(in srgb, var(--color-primary, #3b82f6) 9%, var(--surface-2, #f7f8fb)); }.aksrt-order-detail__hero.is-pending { background: color-mix(in srgb, var(--color-warning, #c87819) 11%, var(--surface-2, #f7f8fb)); }.aksrt-order-detail__hero.is-completed { background: color-mix(in srgb, var(--color-success, #168560) 10%, var(--surface-2, #f7f8fb)); }.aksrt-order-detail__status-pill { font-size: 12px; font-weight: 700; }.aksrt-order-detail__hero-amount { font-size: 23px; font-variant-numeric: tabular-nums; }.aksrt-order-detail__product { display: grid; grid-template-columns: 56px minmax(0, 1fr); gap: 11px; align-items: center; }.aksrt-order-detail__thumb { width: 56px; height: 56px; border-radius: 10px; object-fit: cover; }.aksrt-order-detail__thumb--empty { display: grid; place-items: center; background: var(--surface-2, #f4f6f9); color: var(--color-primary, #3b82f6); }.aksrt-order-detail__product-title { display: block; line-height: 1.4; }.aksrt-order-detail__product-meta { display: block; margin-top: 5px; color: var(--orders-muted); font-size: 12px; }.aksrt-order-detail__list { margin: 0; border: 1px solid var(--orders-line); border-radius: 10px; overflow: hidden; }.aksrt-order-detail__list > div { display: grid; grid-template-columns: 86px minmax(0, 1fr); padding: 10px 12px; border-bottom: 1px solid var(--orders-line); font-size: 12px; }.aksrt-order-detail__list > div:last-child { border-bottom: 0; }.aksrt-order-detail__list dt { color: var(--orders-muted); }.aksrt-order-detail__list dd { margin: 0; overflow-wrap: anywhere; text-align: right; font-variant-numeric: tabular-nums; } -@media (max-width: 767px) { .uc-orders-panel__head { grid-template-columns: 1fr; gap: 17px; }.aksrt-orders-page__stats { max-width: none; }.aksrt-orders-page__search { flex-wrap: wrap; }.aksrt-orders-page__search input { min-width: 150px; }.aksrt-orders-page__search .btn-primary { margin-left: auto; }.aksrt-orders-page__filter-toggle { margin-left: auto; }.aksrt-orders-page__card-rail time { width: 100%; margin-left: 0; order: 3; }.aksrt-orders-page__card-main { grid-template-columns: 48px minmax(0, 1fr); }.aksrt-orders-page__card-img, .aksrt-orders-page__card-art { width: 48px; height: 48px; }.aksrt-orders-page__card-value { grid-column: 2; text-align: left; }.aksrt-orders-page__card-actions > :not(.aksrt-orders-page__countdown) { flex: 1 1 auto; }.aksrt-orders-page__countdown { width: 100%; }.aksrt-modal__footer > * { flex: 1; } } -@media (prefers-reduced-motion: reduce) { .aksrt-orders-page__card { transition: none; } } -html.dark .aksrt-orders-page__stat, html.dark .aksrt-orders-page__card { background: var(--surface-1, #1a1d24); } html.dark .aksrt-orders-page__card-rail, html.dark .aksrt-orders-page__filter-panel, html.dark .aksrt-modal__footer { background: var(--surface-2, #12151b); } +/* Orders extensions */ +.aksrt-orders-page__extra-nav { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 12px; } +.aksrt-orders-page__extra-link { padding: 6px 12px; border-radius: 999px; background: var(--surface-2, #f3f4f6); color: inherit; text-decoration: none; font-size: 13px; } +.aksrt-orders-page__extra-link.is-active { background: var(--focus-color, var(--color-primary)); color: #fff; } +.aksrt-as-list { display: grid; gap: 12px; } +.aksrt-as-card { padding: 14px; border: 1px solid var(--main-border-color, #eee); border-radius: 10px; background: var(--main-bg-color, #fff); } +.aksrt-as-card__head { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 6px; } +.aksrt-as-card__status { margin-left: auto; color: var(--focus-color, #2f6df6); font-weight: 600; } +.aksrt-as-timeline { margin: 10px 0 0; padding-left: 18px; color: var(--muted-2-color, #888); font-size: 12px; } +.aksrt-as-card__actions { display: flex; gap: 8px; margin-top: 10px; } +.aksrt-coupon-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; } +.aksrt-coupon-card { padding: 12px; border: 1px dashed var(--focus-color, #2f6df6); border-radius: 10px; background: color-mix(in srgb, var(--focus-color, #2f6df6) 6%, #fff); } +.aksrt-coupon-card__value { color: var(--focus-color, #2f6df6); font-size: 20px; font-weight: 700; } +.aksrt-coupon-card__code { margin: 6px 0; font-family: ui-monospace, monospace; } +.aksrt-coupon-card__meta { margin-bottom: 4px; color: var(--muted-2-color, #888); font-size: 12px; } +.aksrt-orders-page__field { margin-bottom: 12px; } +.aksrt-orders-page__input, .aksrt-orders-page__select, .aksrt-orders-page__textarea { box-sizing: border-box; width: 100%; padding: 8px 10px; border: 1px solid var(--main-border-color, #ddd); border-radius: 6px; } +@media (max-width: 767px) { .uc-orders-panel__head { grid-template-columns: 1fr; gap: 17px; }.aksrt-orders-page__stats { max-width: none; }.aksrt-orders-page__search { flex-wrap: wrap; }.aksrt-orders-page__search input { min-width: 150px; }.aksrt-orders-page__search .btn-primary { margin-left: auto; }.aksrt-orders-page__filter-toggle { margin-left: auto; }.aksrt-orders-page__card-rail time { width: 100%; margin-left: 0; order: 3; }.aksrt-orders-page__card-main { grid-template-columns: 48px minmax(0, 1fr); }.aksrt-orders-page__card-img, .aksrt-orders-page__card-art { width: 48px; height: 48px; }.aksrt-orders-page__card-value { grid-column: 2; text-align: left; }.aksrt-orders-page__card-actions > :not(.aksrt-orders-page__countdown) { flex: 1 1 auto; }.aksrt-orders-page__countdown { width: 100%; }.aksrt-order-detail__actions > * { flex: 1; } } +html.dark .aksrt-orders-page__stat, html.dark .aksrt-orders-page__card { background: var(--surface-1, #1a1d24); } html.dark .aksrt-orders-page__card-rail, html.dark .aksrt-orders-page__filter-panel, html.dark .aksrt-order-detail__actions { background: var(--surface-2, #12151b); } diff --git a/assets/js/order-center.js b/assets/js/order-center.js index ae5169e7..b21ba788 100644 --- a/assets/js/order-center.js +++ b/assets/js/order-center.js @@ -3,19 +3,10 @@ * * @package AKSRT */ - (function () { "use strict"; - var page = document.querySelector("[data-aksrt-order-center]"); - if (!page) return; - if (!window.AksrtModalRuntime.isRolloutEnabled("order")) return; - - var detailOrderNumber = ""; - var detailAutoRefreshStarted = false; - var detailSession = null; - var detailReplacement = false; - var detailReplacementSession = null; + var instances = new WeakMap(); function toast(message, type) { window.AksrtToast(message, type || "info"); @@ -50,15 +41,6 @@ : "¥" + amount.toFixed(2); } - function activeDetail(number) { - return detailOrderNumber === number - && !!detailSession - && !detailSession.closed - && detailSession.options - && detailSession.options.kind === "orders" - && window.AksrtModalRuntime.getActive() === detailSession.shell; - } - function orderTag(orderNumber) { return "order:" + String(orderNumber || "").toLowerCase(); } @@ -71,6 +53,15 @@ window.AksrtModalRuntime.invalidate(tags); } + function activeDetail(state, number) { + return state.detailOrderNumber === number + && !!state.detailSession + && !state.detailSession.closed + && state.detailSession.options + && state.detailSession.options.kind === "orders" + && window.AksrtModalRuntime.getActive() === state.detailSession.shell; + } + function detailSkeleton() { return '
' + '' + @@ -79,7 +70,7 @@ '
'; } - function renderDetail(order, session) { + function renderDetail(state, order, session) { var allowedActions = order.allowed_actions || {}; var reconciliation = order.reconciliation || {}; var image = order.image || ""; @@ -112,7 +103,7 @@ session.content.innerHTML = html; } - function detailOptions(orderNumber, refresh) { + function detailOptions(state, orderNumber, refresh) { return { url: orderPath(orderNumber, refresh ? "/refresh" : ""), method: refresh ? "POST" : "GET", @@ -124,20 +115,20 @@ cacheTags: ["orders", orderTag(orderNumber)], skeleton: detailSkeleton(), onState: function (_, session) { - if (orderNumber === detailOrderNumber) detailSession = session; + if (orderNumber === state.detailOrderNumber) state.detailSession = session; }, renderData: function (data, session) { - if (orderNumber !== detailOrderNumber) return false; - detailSession = session; + if (orderNumber !== state.detailOrderNumber || !instances.has(state.root)) return false; + state.detailSession = session; if (!data || typeof data !== "object") { session.content.innerHTML = '

订单不存在

'; return; } - renderDetail(data, session); - if (!refresh && !detailAutoRefreshStarted && data.reconciliation && data.reconciliation.state === "refresh_available") { - detailAutoRefreshStarted = true; + renderDetail(state, data, session); + if (!refresh && !state.detailAutoRefreshStarted && data.reconciliation && data.reconciliation.state === "refresh_available") { + state.detailAutoRefreshStarted = true; window.setTimeout(function () { - if (activeDetail(orderNumber)) refreshDetail(orderNumber); + if (activeDetail(state, orderNumber)) refreshDetail(state, orderNumber); }, 0); } }, @@ -147,27 +138,27 @@ }; } - function openDetail(orderNumber) { + function openDetail(state, orderNumber) { if (!orderNumber) return; - detailSession = null; - detailReplacement = false; - detailReplacementSession = null; - detailOrderNumber = orderNumber; - detailAutoRefreshStarted = false; - window.AksrtModalRuntime.openRemote(detailOptions(orderNumber, false)); + state.detailSession = null; + state.detailReplacement = false; + state.detailReplacementSession = null; + state.detailOrderNumber = orderNumber; + state.detailAutoRefreshStarted = false; + window.AksrtModalRuntime.openRemote(detailOptions(state, orderNumber, false)); } - function refreshDetail(orderNumber) { - if (!orderNumber || !activeDetail(orderNumber)) return; - detailReplacement = true; - detailReplacementSession = detailSession; - var refreshPromise = window.AksrtModalRuntime.openRemote(detailOptions(orderNumber, true)); + function refreshDetail(state, orderNumber) { + if (!orderNumber || !activeDetail(state, orderNumber)) return; + state.detailReplacement = true; + state.detailReplacementSession = state.detailSession; + var refreshPromise = window.AksrtModalRuntime.openRemote(detailOptions(state, orderNumber, true)); refreshPromise.then(function () { - detailReplacement = false; - detailReplacementSession = null; + state.detailReplacement = false; + state.detailReplacementSession = null; }, function () { - detailReplacement = false; - detailReplacementSession = null; + state.detailReplacement = false; + state.detailReplacementSession = null; }); } @@ -196,21 +187,21 @@ ? '' : ""; return '
' + - '
' + escapeHtml(order.product_type_label || "") + '' + escapeHtml(order.status_label || "") + '
' + - '
' + image + '

' + escapeHtml(order.product_name || "订单") + '

订单号 ' + escapeHtml(order.order_number) + '

' + formatMoney(order) + '实付
' + - '
'; + '
' + escapeHtml(order.product_type_label || "") + '' + escapeHtml(order.status_label || "") + "
" + + '
' + image + '

' + escapeHtml(order.product_name || "订单") + "

订单号 " + escapeHtml(order.order_number) + "

" + formatMoney(order) + "实付
" + + '"; } - function renderStats(stats) { - var root = page.querySelector("[data-aksrt-order-stats]"); + function renderStats(state, stats) { + var root = state.root.querySelector("[data-aksrt-order-stats]"); if (!root) return; - root.innerHTML = '
全部' + Number(stats.total || 0) + '
' + - '
待支付' + Number(stats.pending || 0) + '
' + - '
已生效' + Number(stats.completed || 0) + '
'; + root.innerHTML = '
全部' + Number(stats.total || 0) + "
" + + '
待支付' + Number(stats.pending || 0) + "
" + + '
已生效' + Number(stats.completed || 0) + "
"; } - function renderPagination(view) { - var existing = page.querySelector(".aksrt-orders-page__pagination"); + function renderPagination(state, view) { + var existing = state.root.querySelector(".aksrt-orders-page__pagination"); if (existing) existing.remove(); if (Number(view.total_pages || 1) <= 1) return; var root = document.createElement("nav"); @@ -225,13 +216,21 @@ link.textContent = String(i); root.appendChild(link); } - var list = page.querySelector("[data-aksrt-order-list]"); + var list = state.root.querySelector("[data-aksrt-order-list]"); if (list) list.after(root); } - function renderView(view) { - renderStats(view.stats || {}); - var list = page.querySelector("[data-aksrt-order-list]"); + function updateCountdowns(state) { + state.root.querySelectorAll("[data-aksrt-order-countdown]").forEach(function (node) { + var value = Math.max(0, Number(node.getAttribute("data-aksrt-order-countdown") || 0) - 1); + node.setAttribute("data-aksrt-order-countdown", String(value)); + node.textContent = value ? "剩余支付时间 " + value + " 秒" : "订单即将关闭"; + }); + } + + function renderView(state, view) { + 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) { @@ -241,14 +240,14 @@ list.classList.remove("is-empty"); list.innerHTML = items.map(cardHtml).join(""); } - renderPagination(view); - updateCountdowns(); + renderPagination(state, view); + updateCountdowns(state); } - function refreshView() { + function refreshView(state) { return request(currentViewPath(), undefined, "GET").then(function (result) { if (!result || !result.data) throw new Error("刷新失败"); - renderView(result.data); + renderView(state, result.data); return result.data; }).catch(function (error) { toast(errorMessage(error, "订单状态已变更,请刷新后查看")); @@ -256,7 +255,7 @@ }); } - function startPayment(button) { + function startPayment(state, button) { var number = button.getAttribute("data-aksrt-order-pay") || ""; var method = button.getAttribute("data-aksrt-order-method") || ""; if (!number || button.disabled) return; @@ -269,15 +268,15 @@ return; } toast(data.message || "支付成功", "success"); - refreshView(); - if (activeDetail(number)) openDetail(number); + refreshView(state); + if (activeDetail(state, number)) openDetail(state, number); }).catch(function (error) { button.disabled = false; toast(errorMessage(error, "网络错误,请稍后重试"), "error"); }); } - function closeOrder(button) { + function closeOrder(state, button) { var number = button.getAttribute("data-aksrt-order-cancel") || ""; if (!number || button.disabled) return; if (button.getAttribute("data-aksrt-confirm") !== "true") { @@ -290,8 +289,8 @@ var data = result.data || {}; invalidateOrder(number, data.order); toast("订单已关闭", "success"); - refreshView(); - if (activeDetail(number)) openDetail(number); + refreshView(state); + if (activeDetail(state, number)) openDetail(state, number); }).catch(function (error) { button.disabled = false; button.removeAttribute("data-aksrt-confirm"); @@ -300,61 +299,104 @@ }); } - function updateCountdowns() { - page.querySelectorAll("[data-aksrt-order-countdown]").forEach(function (node) { - var value = Math.max(0, Number(node.getAttribute("data-aksrt-order-countdown") || 0) - 1); - node.setAttribute("data-aksrt-order-countdown", String(value)); - node.textContent = value ? "剩余支付时间 " + value + " 秒" : "订单即将关闭"; + function closest(target, selector) { + return target && target.closest ? target.closest(selector) : null; + } + + function initRoot(root) { + if (instances.has(root) || !window.AksrtModalRuntime.isRolloutEnabled("order")) return; + var state = { + root: root, + detailOrderNumber: "", + detailAutoRefreshStarted: false, + detailSession: null, + detailReplacement: false, + detailReplacementSession: null, + countdownTimer: null, + }; + + state.onRootClick = function (event) { + var filterToggle = closest(event.target, "[data-aksrt-orders-filter-toggle]"); + if (filterToggle && root.contains(filterToggle)) { + var panel = root.querySelector("[data-aksrt-orders-filter-panel]"); + var expanded = filterToggle.getAttribute("aria-expanded") === "true"; + filterToggle.setAttribute("aria-expanded", String(!expanded)); + if (panel) panel.hidden = expanded; + 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]"); + if (pay && root.contains(pay)) { event.preventDefault(); startPayment(state, pay); return; } + var cancel = closest(event.target, "[data-aksrt-order-cancel]"); + if (cancel && root.contains(cancel)) { event.preventDefault(); closeOrder(state, cancel); } + }; + state.onModalClose = function (event) { + var detail = event && event.detail ? event.detail : {}; + if (!state.detailSession || detail.session !== state.detailSession) return; + if (detail.reason === "replace" && state.detailReplacement && detail.session === state.detailReplacementSession) return; + state.detailSession = null; + state.detailOrderNumber = ""; + state.detailAutoRefreshStarted = false; + state.detailReplacement = false; + state.detailReplacementSession = null; + }; + state.onDocumentClick = function (event) { + if (!state.detailSession || window.AksrtModalRuntime.getActive() !== state.detailSession.shell) return; + var refresh = closest(event.target, "[data-aksrt-order-refresh]"); + if (refresh) { + event.preventDefault(); + refreshDetail(state, refresh.getAttribute("data-aksrt-order-refresh") || ""); + return; + } + var pay = closest(event.target, "[data-aksrt-order-pay]"); + if (pay && !root.contains(pay)) { + event.preventDefault(); + startPayment(state, pay); + return; + } + var cancel = closest(event.target, "[data-aksrt-order-cancel]"); + if (cancel && !root.contains(cancel)) { + event.preventDefault(); + closeOrder(state, cancel); + } + }; + + root.addEventListener("click", state.onRootClick); + document.addEventListener("aksrt:modal:close", state.onModalClose); + document.addEventListener("click", state.onDocumentClick); + state.countdownTimer = window.setInterval(function () { updateCountdowns(state); }, 1000); + instances.set(root, state); + } + + function rootsIn(root) { + if (!root) return []; + var roots = []; + if (root.matches && root.matches("[data-aksrt-order-center]")) roots.push(root); + if (root.querySelectorAll) root.querySelectorAll("[data-aksrt-order-center]").forEach(function (item) { roots.push(item); }); + return roots; + } + + function init(root) { + rootsIn(root || document).forEach(initRoot); + } + + function destroy(root) { + rootsIn(root).forEach(function (item) { + var state = instances.get(item); + if (!state) return; + if (activeDetail(state, state.detailOrderNumber)) window.AksrtModalRuntime.close("replace"); + item.removeEventListener("click", state.onRootClick); + document.removeEventListener("aksrt:modal:close", state.onModalClose); + document.removeEventListener("click", state.onDocumentClick); + window.clearInterval(state.countdownTimer); + instances.delete(item); }); } - page.addEventListener("click", function (event) { - var filterToggle = event.target.closest("[data-aksrt-orders-filter-toggle]"); - if (filterToggle) { - var panel = page.querySelector("[data-aksrt-orders-filter-panel]"); - var expanded = filterToggle.getAttribute("aria-expanded") === "true"; - filterToggle.setAttribute("aria-expanded", String(!expanded)); - if (panel) panel.hidden = expanded; - return; - } - var detail = event.target.closest("[data-aksrt-order-detail]"); - if (detail) { openDetail(detail.getAttribute("data-aksrt-order-detail")); return; } - var pay = event.target.closest("[data-aksrt-order-pay]"); - if (pay) { event.preventDefault(); startPayment(pay); return; } - var cancel = event.target.closest("[data-aksrt-order-cancel]"); - if (cancel) { event.preventDefault(); closeOrder(cancel); } - }); - - document.addEventListener("aksrt:modal:close", function (event) { - var detail = event && event.detail ? event.detail : {}; - if (!detailSession || detail.session !== detailSession) return; - if (detail.reason === "replace" && detailReplacement && detail.session === detailReplacementSession) return; - detailSession = null; - detailOrderNumber = ""; - detailAutoRefreshStarted = false; - detailReplacement = false; - detailReplacementSession = null; - }); - - document.addEventListener("click", function (event) { - var refresh = event.target.closest("[data-aksrt-order-refresh]"); - if (refresh) { - event.preventDefault(); - refreshDetail(refresh.getAttribute("data-aksrt-order-refresh") || ""); - return; - } - var pay = event.target.closest("[data-aksrt-order-pay]"); - if (pay && !page.contains(pay)) { - event.preventDefault(); - startPayment(pay); - return; - } - var cancel = event.target.closest("[data-aksrt-order-cancel]"); - if (cancel && !page.contains(cancel)) { - event.preventDefault(); - closeOrder(cancel); - } - }); - - window.setInterval(updateCountdowns, 1000); + window.AKSRTOrderCenter = { init: init, destroy: destroy }; + if (window.AksrtFrontend && typeof window.AksrtFrontend.register === "function") { + window.AksrtFrontend.register("aksrt-order-center", init); + } + init(document); }()); diff --git a/assets/js/routes/user-center.js b/assets/js/routes/user-center.js index 0deb2aac..5cceb3eb 100644 --- a/assets/js/routes/user-center.js +++ b/assets/js/routes/user-center.js @@ -77,6 +77,9 @@ if (target._aksrtPanelRequest) return target._aksrtPanelRequest; var restUrl = (window.aksrtData && (window.aksrtData.restUrl || window.aksrtData.apiUrl)) || cfg.restUrl || ''; if (!restUrl) return Promise.reject(new Error('rest-unavailable')); + if (name === 'orders' && (!window.AKSRTOrderCenter || typeof window.AKSRTOrderCenter.init !== 'function')) { + return Promise.reject(new Error('orders-initializer-unavailable')); + } target.classList.add('is-loading'); var url = restUrl.replace(/\/$/, '') + '/user-center/panel/' + encodeURIComponent(name); if (name === 'creator') { @@ -91,7 +94,13 @@ }).then(function(response) { return response.json().then(function(payload) { if (!response.ok || !payload || !payload.success || !payload.data || typeof payload.data.html !== 'string') throw new Error('panel-load-failed'); + if (name === 'orders' && window.AKSRTOrderCenter && typeof window.AKSRTOrderCenter.destroy === 'function') { + window.AKSRTOrderCenter.destroy(target); + } target.innerHTML = payload.data.html; + if (window.AksrtFrontend && typeof window.AksrtFrontend.rehydrate === 'function') { + window.AksrtFrontend.rehydrate(target, 'ajax'); + } target.dataset.panelLoaded = 'true'; }); }).finally(function() { diff --git a/src/Core/ScriptsPart1b.php b/src/Core/ScriptsPart1b.php index 03608fe2..c7b477fc 100644 --- a/src/Core/ScriptsPart1b.php +++ b/src/Core/ScriptsPart1b.php @@ -462,6 +462,36 @@ $hmb_js = get_template_directory() . '/assets/js/header-newadd.js'; ['aksrt-route-profile'], file_exists($user_center_css_path) ? (string) filemtime($user_center_css_path) : $version ); + // Orders is a lazy user-center panel, so its independent assets must + // be ready before client-side panel replacement can insert its markup. + $has_orders_panel = false; + if (class_exists('\AKSRT\User\Center')) { + $dashboard_tabs = \AKSRT\User\Center::get_instance()->get_dashboard_tabs(); + $has_orders_panel = isset($dashboard_tabs['orders']); + } + if ($has_orders_panel) { + $orders_css = get_template_directory() . '/assets/css/routes/orders.css'; + if (file_exists($orders_css)) { + wp_enqueue_style( + 'aksrt-orders', + $base_uri . '/assets/css/routes/orders.css', + ['aksrt-tokens'], + (string) filemtime($orders_css) + ); + } + + $order_center_js = get_template_directory() . '/assets/js/order-center.js'; + if (file_exists($order_center_js)) { + wp_enqueue_script( + 'aksrt-order-center', + $base_uri . '/assets/js/order-center.js', + ['aksrt-script', 'aksrt-rehydrate', 'aksrt-core-request', 'aksrt-core-modal-runtime'], + (string) filemtime($order_center_js), + true + ); + } + } + $identity_js = get_template_directory() . '/assets/js/routes/user-center-auth.js'; if (file_exists($identity_js)) { wp_enqueue_script( @@ -472,10 +502,16 @@ $hmb_js = get_template_directory() . '/assets/js/header-newadd.js'; true ); } + $user_center_deps = file_exists($identity_js) + ? ['aksrt-script', 'aksrt-route-user-center-auth'] + : ['aksrt-script']; + if ($has_orders_panel && wp_script_is('aksrt-order-center', 'registered')) { + $user_center_deps[] = 'aksrt-order-center'; + } wp_enqueue_script( 'aksrt-route-user-center', $base_uri . '/assets/js/routes/user-center.js', - file_exists($identity_js) ? ['aksrt-script', 'aksrt-route-user-center-auth'] : ['aksrt-script'], + $user_center_deps, file_exists(get_template_directory() . '/assets/js/routes/user-center.js') ? (string) filemtime(get_template_directory() . '/assets/js/routes/user-center.js') : $version, @@ -671,30 +707,30 @@ $hmb_js = get_template_directory() . '/assets/js/header-newadd.js'; } } - // 订单中心:独立页 / 虚拟 rewrite / 用户中心 tab=orders + // 订单中心:独立页 / 虚拟 rewrite;用户中心在上方按可用 tab 预载。 $is_orders_surface = is_page_template('templates/pages/page-orders.php') - || (bool) get_query_var('aksrt_orders') - || ( - (function_exists('aksrt_is_user_center_page') && aksrt_is_user_center_page()) - && isset($_GET['tab']) - && sanitize_key((string) wp_unslash($_GET['tab'])) === 'orders' - ); + || (bool) get_query_var('aksrt_orders'); if ($is_orders_surface) { - wp_enqueue_style( - 'aksrt-orders', - $base_uri . '/assets/css/routes/orders.css', - ['aksrt-tokens'], - $version - ); - wp_enqueue_script( - 'aksrt-order-center', - $base_uri . '/assets/js/order-center.js', - ['aksrt-script', 'aksrt-core-request', 'aksrt-core-modal-runtime'], - file_exists(get_template_directory() . '/assets/js/order-center.js') - ? (string) filemtime(get_template_directory() . '/assets/js/order-center.js') - : $version, - true - ); + $orders_css = get_template_directory() . '/assets/css/routes/orders.css'; + if (file_exists($orders_css)) { + wp_enqueue_style( + 'aksrt-orders', + $base_uri . '/assets/css/routes/orders.css', + ['aksrt-tokens'], + (string) filemtime($orders_css) + ); + } + + $order_center_js = get_template_directory() . '/assets/js/order-center.js'; + if (file_exists($order_center_js)) { + wp_enqueue_script( + 'aksrt-order-center', + $base_uri . '/assets/js/order-center.js', + ['aksrt-script', 'aksrt-rehydrate', 'aksrt-core-request', 'aksrt-core-modal-runtime'], + (string) filemtime($order_center_js), + true + ); + } } // 收益中心:旧路由重定向页 / 用户中心 tab=revenue。