feat: preserve supplier page state during local navigation

This commit is contained in:
renshen 2026-09-12 13:36:08 +08:00
parent 2394dc912a
commit 6b3632fa65
3 changed files with 81 additions and 0 deletions

View File

@ -546,6 +546,7 @@
'business-page--marketing-account-balance' => $isMarketingAccountBalancePage,
])
@if ($isMarketingAccountBillsPage) data-marketing-account-bills-workspace @endif
@if ($section['slug'] === 'supplier-payables') data-supplier-workspace @endif
>
@if ($isMarketingAccountBillsPage)
<p class="sr-only" role="status" aria-live="polite" data-marketing-account-bills-status></p>

View File

@ -0,0 +1,78 @@
@push('body_end')
<script>
(() => {
const selector = '[data-supplier-workspace]';
const initial = document.querySelector(selector);
if (!initial) return;
const cache = new Map();
const loaded = new Set([...document.scripts].map(script => script.src));
let current = location.href;
let pending = false;
const heading = document.querySelector('#main-body h1');
const remember = () => cache.set(current, {
node: document.querySelector(selector),
title: document.title,
heading: heading ? heading.innerHTML : '',
});
remember();
async function navigate(href, push = true) {
if (pending) return;
const previous = document.querySelector(selector);
pending = true;
previous.setAttribute('aria-busy', 'true');
const status = previous.querySelector('[data-supplier-navigation-status]');
status.textContent = '正在加载…';
try {
let next = cache.get(href);
let scripts = [];
if (!next) {
const response = await fetch(href, {credentials: 'same-origin', headers: {Accept: 'text/html'}});
if (!response.ok) throw new Error('HTTP ' + response.status);
const doc = new DOMParser().parseFromString(await response.text(), 'text/html');
const node = doc.querySelector(selector);
if (!node) throw new Error('页面不可用或登录已过期');
scripts = [...doc.querySelectorAll('script[src]')].map(s => s.src)
.filter(src => new URL(src).origin === location.origin && new URL(src).pathname.startsWith('/public/js/ecommerce-finance/') && !loaded.has(src));
next = {node: document.importNode(node, true), title: doc.title, heading: doc.querySelector('#main-body h1')?.innerHTML || ''};
}
remember();
previous.replaceWith(next.node);
// Keep each mounted Vue page alive while switching, preserving its filters and pagination.
for (const src of scripts) {
await new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.onload = () => { loaded.add(src); resolve(); };
script.onerror = () => { script.remove(); reject(new Error('页面脚本加载失败')); };
document.body.appendChild(script);
});
}
cache.set(href, next);
current = href;
document.title = next.title;
if (heading) heading.innerHTML = next.heading;
next.node.querySelector('[data-supplier-navigation-status]').textContent = '';
if (push && location.href !== href) history.pushState({}, '', href);
next.node.querySelector('.supplier-page-navigation [aria-current]')?.focus({preventScroll: true});
} catch (error) {
const visible = document.querySelector(selector);
if (visible !== previous) visible.replaceWith(previous);
status.textContent = '加载失败,原页面已保留。请重新点击目录重试。';
} finally {
previous.removeAttribute('aria-busy');
pending = false;
}
}
document.addEventListener('click', event => {
const link = event.target.closest('.supplier-page-navigation a');
if (!link || event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) return;
event.preventDefault();
navigate(link.href);
});
window.addEventListener('popstate', () => {
if (location.pathname.includes('/workspace/supplier-payables/')) navigate(location.href, false);
});
})();
</script>
@endpush

View File

@ -2,6 +2,7 @@
$supplierPages = $section['pages'] ?? [];
$supplierCurrent = $page['slug'] ?? null;
@endphp
<p role="status" aria-live="polite" data-supplier-navigation-status></p>
<nav class="business-tab-bar supplier-page-navigation" aria-label="供应商应付页面">
@foreach ($supplierPages as $supplierPage)
@php
@ -29,3 +30,4 @@
.supplier-page-navigation .business-tab-link { flex:0 0 auto; padding:10px 0; font-size:0.75rem; color:var(--vip-content-muted, #595959); }
.supplier-page-navigation .business-tab-link.is-active { color:var(--vip-content-accent-dark, #856632); }
</style>
@include('business.pages.supplier-payables.local-navigation')