Added Page Leave Guard
This commit is contained in:
98
components/PageLeaveGuard.vue
Normal file
98
components/PageLeaveGuard.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { onBeforeRouteLeave } from 'vue-router'
|
||||
|
||||
// Wir erwarten eine Prop, die sagt, ob geschützt werden soll
|
||||
const props = defineProps<{
|
||||
when: boolean // z.B. true, wenn Formular dirty ist
|
||||
}>()
|
||||
|
||||
const showModal = ref(false)
|
||||
const pendingNext = ref<null | ((val?: boolean) => void)>(null)
|
||||
|
||||
// --- 1. Interne Navigation (Nuxt) ---
|
||||
onBeforeRouteLeave((to, from, next) => {
|
||||
if (!props.when) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
// Navigation pausieren & Modal zeigen
|
||||
pendingNext.value = next
|
||||
showModal.value = true
|
||||
})
|
||||
|
||||
const confirmLeave = () => {
|
||||
if (pendingNext.value) pendingNext.value()
|
||||
showModal.value = false
|
||||
}
|
||||
|
||||
const cancelLeave = () => {
|
||||
showModal.value = false
|
||||
// Navigation wird implizit abgebrochen
|
||||
}
|
||||
|
||||
// --- 2. Externe Navigation (Browser Tab schließen) ---
|
||||
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
||||
if (props.when) {
|
||||
e.preventDefault()
|
||||
e.returnValue = ''
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => window.addEventListener('beforeunload', handleBeforeUnload))
|
||||
onBeforeUnmount(() => window.removeEventListener('beforeunload', handleBeforeUnload))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div v-if="showModal" class="guard-overlay">
|
||||
<div class="guard-modal">
|
||||
|
||||
<div class="guard-header">
|
||||
<slot name="title">Seite wirklich verlassen?</slot>
|
||||
</div>
|
||||
|
||||
<div class="guard-body">
|
||||
<slot>
|
||||
Du hast ungespeicherte Änderungen. Diese gehen verloren, wenn du die Seite verlässt.
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<div class="guard-actions">
|
||||
<button @click="cancelLeave" class="btn-cancel">
|
||||
Nein, bleiben
|
||||
</button>
|
||||
<button @click="confirmLeave" class="btn-confirm">
|
||||
Ja, verlassen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Basis-Styling - passe dies an dein Design System an */
|
||||
.guard-overlay {
|
||||
position: fixed; inset: 0; background: rgba(0,0,0,0.6);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
z-index: 9999; backdrop-filter: blur(2px);
|
||||
}
|
||||
.guard-modal {
|
||||
background: white; padding: 24px; border-radius: 12px;
|
||||
width: 90%; max-width: 400px; box-shadow: 0 10px 25px rgba(0,0,0,0.2);
|
||||
font-family: sans-serif;
|
||||
}
|
||||
.guard-header { font-size: 1.25rem; font-weight: bold; margin-bottom: 1rem; }
|
||||
.guard-body { margin-bottom: 1.5rem; color: #4a5568; }
|
||||
.guard-actions { display: flex; justify-content: flex-end; gap: 12px; }
|
||||
|
||||
/* Buttons */
|
||||
button { padding: 8px 16px; border-radius: 6px; cursor: pointer; border: none; font-weight: 600;}
|
||||
.btn-cancel { background: #edf2f7; color: #2d3748; }
|
||||
.btn-cancel:hover { background: #e2e8f0; }
|
||||
.btn-confirm { background: #e53e3e; color: white; }
|
||||
.btn-confirm:hover { background: #c53030; }
|
||||
</style>
|
||||
@@ -3136,6 +3136,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
||||
</UTabs>
|
||||
<UProgress animation="carousel" v-else/>
|
||||
</UDashboardPanelContent>
|
||||
<PageLeaveGuard :when="true"/>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -575,6 +575,7 @@ const findIncomingInvoiceErrors = computed(() => {
|
||||
<UProgress v-else animation="carousel"/>
|
||||
</UDashboardPanelContent>
|
||||
|
||||
<PageLeaveGuard :when="true"/>
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user