mirror of
https://github.com/pure-admin/vue-pure-admin.git
synced 2025-06-09 01:47:20 +08:00
149 lines
5.6 KiB
JavaScript
149 lines
5.6 KiB
JavaScript
import { reactive, inject, markRaw, nextTick, readonly } from 'vue';
|
|
export const RouterSymbol = Symbol();
|
|
// we are just using URL to parse the pathname and hash - the base doesn't
|
|
// matter and is only passed to support same-host hrefs.
|
|
const fakeHost = `http://a.com`;
|
|
const getDefaultRoute = () => ({
|
|
path: '/',
|
|
component: null,
|
|
// this will be set upon initial page load, which is before
|
|
// the app is mounted, so it's guaranteed to be avaiable in
|
|
// components
|
|
data: null
|
|
});
|
|
export function createRouter(loadPageModule, fallbackComponent) {
|
|
const route = reactive(getDefaultRoute());
|
|
const inBrowser = typeof window !== 'undefined';
|
|
function go(href = inBrowser ? location.href : '/') {
|
|
// ensure correct deep link so page refresh lands on correct files.
|
|
const url = new URL(href, fakeHost);
|
|
if (!url.pathname.endsWith('/') && !url.pathname.endsWith('.html')) {
|
|
url.pathname += '.html';
|
|
href = url.pathname + url.search + url.hash;
|
|
}
|
|
if (inBrowser) {
|
|
// save scroll position before changing url
|
|
history.replaceState({ scrollPosition: window.scrollY }, document.title);
|
|
history.pushState(null, '', href);
|
|
}
|
|
return loadPage(href);
|
|
}
|
|
let latestPendingPath = null;
|
|
async function loadPage(href, scrollPosition = 0) {
|
|
const targetLoc = new URL(href, fakeHost);
|
|
const pendingPath = (latestPendingPath = targetLoc.pathname);
|
|
try {
|
|
let page = loadPageModule(pendingPath);
|
|
// only await if it returns a Promise - this allows sync resolution
|
|
// on initial render in SSR.
|
|
if ('then' in page && typeof page.then === 'function') {
|
|
page = await page;
|
|
}
|
|
if (latestPendingPath === pendingPath) {
|
|
latestPendingPath = null;
|
|
const { default: comp, __pageData } = page;
|
|
if (!comp) {
|
|
throw new Error(`Invalid route component: ${comp}`);
|
|
}
|
|
route.path = pendingPath;
|
|
route.component = markRaw(comp);
|
|
route.data = readonly(JSON.parse(__pageData));
|
|
if (inBrowser) {
|
|
nextTick(() => {
|
|
if (targetLoc.hash && !scrollPosition) {
|
|
const target = document.querySelector(decodeURIComponent(targetLoc.hash));
|
|
if (target) {
|
|
scrollTo(target, targetLoc.hash);
|
|
return;
|
|
}
|
|
}
|
|
window.scrollTo(0, scrollPosition);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
catch (err) {
|
|
if (!err.message.match(/fetch/)) {
|
|
console.error(err);
|
|
}
|
|
if (latestPendingPath === pendingPath) {
|
|
latestPendingPath = null;
|
|
route.path = pendingPath;
|
|
route.component = fallbackComponent ? markRaw(fallbackComponent) : null;
|
|
}
|
|
}
|
|
}
|
|
if (inBrowser) {
|
|
window.addEventListener('click', (e) => {
|
|
const link = e.target.closest('a');
|
|
if (link) {
|
|
const { href, protocol, hostname, pathname, hash, target } = link;
|
|
const currentUrl = window.location;
|
|
// only intercept inbound links
|
|
if (!e.ctrlKey &&
|
|
!e.shiftKey &&
|
|
!e.altKey &&
|
|
!e.metaKey &&
|
|
target !== `_blank` &&
|
|
protocol === currentUrl.protocol &&
|
|
hostname === currentUrl.hostname) {
|
|
e.preventDefault();
|
|
if (pathname === currentUrl.pathname) {
|
|
// scroll between hash anchors in the same page
|
|
if (hash && hash !== currentUrl.hash) {
|
|
history.pushState(null, '', hash);
|
|
// use smooth scroll when clicking on header anchor links
|
|
scrollTo(link, hash, link.classList.contains('header-anchor'));
|
|
}
|
|
}
|
|
else {
|
|
go(href);
|
|
}
|
|
}
|
|
}
|
|
}, { capture: true });
|
|
window.addEventListener('popstate', (e) => {
|
|
loadPage(location.href, (e.state && e.state.scrollPosition) || 0);
|
|
});
|
|
window.addEventListener('hashchange', (e) => {
|
|
e.preventDefault();
|
|
});
|
|
}
|
|
return {
|
|
route,
|
|
go
|
|
};
|
|
}
|
|
export function useRouter() {
|
|
const router = inject(RouterSymbol);
|
|
if (!router) {
|
|
throw new Error('useRouter() is called without provider.');
|
|
}
|
|
// @ts-ignore
|
|
return router;
|
|
}
|
|
export function useRoute() {
|
|
return useRouter().route;
|
|
}
|
|
function scrollTo(el, hash, smooth = false) {
|
|
const pageOffset = document.querySelector('.navbar')
|
|
.offsetHeight;
|
|
const target = el.classList.contains('.header-anchor')
|
|
? el
|
|
: document.querySelector(decodeURIComponent(hash));
|
|
if (target) {
|
|
const targetTop = target.offsetTop - pageOffset - 15;
|
|
// only smooth scroll if distance is smaller than screen height.
|
|
if (!smooth || Math.abs(targetTop - window.scrollY) > window.innerHeight) {
|
|
window.scrollTo(0, targetTop);
|
|
}
|
|
else {
|
|
window.scrollTo({
|
|
left: 0,
|
|
top: targetTop,
|
|
behavior: 'smooth'
|
|
});
|
|
}
|
|
}
|
|
}
|