/** * 관리자 편집 화면의 미저장 변경 이탈을 막는다. * @param {import('vue').Ref | import('vue').ComputedRef} isDirty - 변경 여부 * @param {{ onLeaveConfirmed?: () => void | Promise }} options - 이탈 승인 옵션 * @returns {{ * isUnsavedModalOpen: import('vue').Ref, * stayOnUnsavedPage: () => void, * leaveUnsavedPage: () => Promise, * allowNextRouteLeave: () => void * }} 이탈 확인 상태와 동작 */ export const useAdminUnsavedChangesGuard = (isDirty, options = {}) => { const isUnsavedModalOpen = ref(false) const pendingRoute = ref(null) const isNextRouteAllowed = ref(false) /** * 현재 이탈을 막아야 하는지 확인한다. * @returns {boolean} 이탈 차단 여부 */ const shouldBlockLeave = () => Boolean(unref(isDirty)) && !isNextRouteAllowed.value /** * 다음 라우트 이동을 한 번 허용한다. * @returns {void} */ const allowNextRouteLeave = () => { isNextRouteAllowed.value = true } /** * 현재 페이지에 머문다. * @returns {void} */ const stayOnUnsavedPage = () => { pendingRoute.value = null isUnsavedModalOpen.value = false } /** * 미저장 변경을 버리고 이동한다. * @returns {Promise} */ const leaveUnsavedPage = async () => { const route = pendingRoute.value pendingRoute.value = null isUnsavedModalOpen.value = false isNextRouteAllowed.value = true await options.onLeaveConfirmed?.() if (route?.fullPath) { await navigateTo(route.fullPath) } } onBeforeRouteLeave((to) => { if (isNextRouteAllowed.value) { isNextRouteAllowed.value = false return true } if (!shouldBlockLeave()) { return true } pendingRoute.value = to isUnsavedModalOpen.value = true return false }) /** * 브라우저 탭 닫기와 새로고침을 기본 확인창으로 막는다. * @param {BeforeUnloadEvent} event - 브라우저 이탈 이벤트 * @returns {void} */ const handleBeforeUnload = (event) => { if (!shouldBlockLeave()) { return } event.preventDefault() event.returnValue = '' } onMounted(() => { window.addEventListener('beforeunload', handleBeforeUnload) }) onBeforeUnmount(() => { window.removeEventListener('beforeunload', handleBeforeUnload) }) return { isUnsavedModalOpen, stayOnUnsavedPage, leaveUnsavedPage, allowNextRouteLeave } }