scroll restore

This commit is contained in:
Gauthier Roebroeck 2026-01-21 14:54:10 +08:00
parent 89fa902590
commit 60a2e5bda1
10 changed files with 56 additions and 6 deletions

View file

@ -9,4 +9,5 @@ series.vue
<route lang="yaml">
meta:
requiresRole: USER
scrollable: true
</route>

View file

@ -10,4 +10,5 @@
<route lang="yaml">
meta:
requiresRole: USER
scrollable: true
</route>

View file

@ -10,4 +10,5 @@
<route lang="yaml">
meta:
requiresRole: USER
scrollable: true
</route>

View file

@ -123,4 +123,5 @@ watch(series, (newSeries) => {
<route lang="yaml">
meta:
requiresRole: USER
scrollable: true
</route>

View file

@ -18,6 +18,7 @@ import type { App } from 'vue'
// Navigation guards
import { useLoginGuard } from '@/router/login-guard'
import { useRoleGuard } from '@/router/role-guard'
import { useScroll } from '@/router/scroll'
export function registerPlugins(app: App) {
app
@ -39,4 +40,5 @@ export function registerPlugins(app: App) {
// register navigation guards
useLoginGuard(router)
useRoleGuard(router)
useScroll(router)
}

View file

@ -9,11 +9,24 @@ import { createRouter, createWebHistory } from 'vue-router'
import { setupLayouts } from 'virtual:generated-layouts'
import { routes } from 'vue-router/auto-routes'
import { scrollerQuery, scrollMap } from '@/router/scroll'
const router = createRouter({
history: createWebHistory(
import.meta.env.PROD ? window.resourceBaseUrl : import.meta.env.BASE_URL,
),
routes: setupLayouts(routes),
scrollBehavior(to) {
if (scrollMap.has(to.path)) {
const scrollTo = scrollMap.get(to.path)
return new Promise((resolve) => {
void nextTick(() => {
document.querySelector(scrollerQuery)?.scrollTo(0, scrollTo ?? 0)
resolve(false)
})
})
}
},
})
// Workaround for https://github.com/vitejs/vite/issues/11804

View file

@ -1,9 +1,11 @@
import type { Router } from 'vue-router'
import { useCurrentUser } from '@/colada/users'
// check if the user is authenticated before navigating to any page
// the authentication is cached by Pinia Colada
// redirect to the startup page if not authenticated
/**
* Check if the user is authenticated before navigating to any page.
* The authentication is cached by Pinia Colada.
* Redirect to the startup page if not authenticated.
*/
export function useLoginGuard(router: Router) {
router.beforeEach((to) => {
if (!to.meta.noAuth) {

View file

@ -1,9 +1,11 @@
import type { Router } from 'vue-router'
import { useCurrentUser } from '@/colada/users'
// check if the user has the necessary role before navigating to restricted pages
// the authentication is cached by Pinia Colada
// redirect to the home page in case of insufficient permissions
/**
* Check if the user has the necessary role before navigating to restricted pages.
* The authentication is cached by Pinia Colada.
* Redirect to the home page in case of insufficient permissions.
*/
export function useRoleGuard(router: Router) {
router.beforeEach((to) => {
if (to.meta.requiresRole) {

View file

@ -0,0 +1,23 @@
import type { Router } from 'vue-router'
/**
* Stores the `scrollTop` position per `route.path`
*/
export const scrollMap = new Map<string, number>()
/**
* Query to be used with `document.querySelector()` to get the main scroller.
*/
export const scrollerQuery = '.v-main__scroller'
/**
* Persist the scroll position when navigating away from a page.
* Scroll restoration is handled in the router's `scrollBehavior` function.
*/
export function useScroll(router: Router) {
router.afterEach((to, from) => {
if (from.meta.scrollable) {
const scroller = document.querySelector(scrollerQuery)
if (scroller) scrollMap.set(from.path, scroller.scrollTop)
}
})
}

View file

@ -11,5 +11,9 @@ declare module 'vue-router' {
interface RouteMeta {
noAuth?: boolean
requiresRole?: UserRoles
/**
* Indicates whether the scroll position should be persisted and restored for this page.
*/
scrollable?: boolean
}
}