import { mdiCloseCircle, mdiCog, mdiFileDocumentOutline, mdiFileTreeOutline, mdiHome, mdiPlusCircle, mdiTableEdit, } from '@mdi/js' import { computed, onBeforeUnmount, onMounted, watch } from 'vue' import { useRoute, useRouter } from 'vue-router' import { SESSION_FORCE_LOGOUT_EVENT } from '@/services/session' import { useAuthStore } from '@/stores/auth' import { useBreadcrumbStore } from '@/stores/breadcrumbs' import { useFavoritesStore } from '@/stores/favorites' import { useMenuStore, type LayoutMenuItem } from '@/stores/menu' import { useMessageStore } from '@/stores/messages' import { useSnackbarStore } from '@/stores/snackbar' const fixedMenuItems: LayoutMenuItem[] = [ { title: '資料維護', navigable: false, subItems: [ { title: '單筆資料維護', icon: mdiFileDocumentOutline, path: '/single-record-maintenance' }, { title: '主從資料維護A', icon: mdiFileTreeOutline, path: '/master-detail-maintenance' }, { title: '主從資料維護B', icon: mdiFileTreeOutline, path: '/master-detail-maintenance-b' }, { title: '主從資料維護C', icon: mdiFileTreeOutline, path: '/master-detail-maintenance-c' }, { title: '可編輯表格維護', icon: mdiTableEdit, path: '/editable-grid-maintenance' }, ], }, { title: '登入頁', path: '/login' }, ] const menuItemsExample: LayoutMenuItem[] = [ { title: '首頁', icon: mdiHome, path: '/' }, { title: '設定', icon: mdiCog, path: '/settings', navigable: false, }, ...fixedMenuItems, ] function buildMergedMenuItems(items: LayoutMenuItem[]) { const flatPaths = new Set() const collectPaths = (list: LayoutMenuItem[]) => { for (const item of list || []) { if (item?.path) flatPaths.add(item.path) if (item?.subItems?.length) collectPaths(item.subItems) } } collectPaths(items) const mergeFixedItems = (list: LayoutMenuItem[]) => { return (list || []).map((item) => { if (!item?.subItems?.length) return item const subItems = item.subItems.filter((sub) => !sub?.path || !flatPaths.has(sub.path)) return { ...item, subItems } }) } const filteredFixedItems = mergeFixedItems(fixedMenuItems).filter((item) => { if (!item?.subItems?.length) return !item?.path || !flatPaths.has(item.path) return item.subItems.length > 0 }) return [...(items || []), ...filteredFixedItems] } type UseAppShellOptions = { onLogout?: () => void } export function useAppShell(options: UseAppShellOptions = {}) { const route = useRoute() const router = useRouter() const snackbar = useSnackbarStore() const authStore = useAuthStore() const menuStore = useMenuStore() const breadcrumbStore = useBreadcrumbStore() const favoritesStore = useFavoritesStore() const messageStore = useMessageStore() const mergedMenuItems = computed(() => buildMergedMenuItems(menuStore.menuItems)) const mergedFavoriteItems = computed(() => { const combined = [...menuStore.favoriteItems, ...favoritesStore.layoutItems] const seen = new Set() return combined.filter((item) => { const key = item.path ?? item.title if (!key) return false if (seen.has(key)) return false seen.add(key) return true }) }) const layoutProps = computed(() => { const layout = route.meta.layout if (layout === 'default') { return { systemTitle: '測試環境', favoriteItems: mergedFavoriteItems.value, menuItems: mergedMenuItems.value, breadcrumbItems: breadcrumbStore.breadcrumbItems, } } return {} }) function handleSelect(item: LayoutMenuItem) { if (item.path) { router.push(item.path) } } function recursiveFindTitle(path: string, items: LayoutMenuItem[]): string | null { for (const item of items) { if (item.path === path) return item.title if (item.subItems?.length) { const found = recursiveFindTitle(path, item.subItems) if (found) return found } } return null } function findTitle(path: string) { const menuTitle = recursiveFindTitle(path, menuStore.menuItems) if (menuTitle) return menuTitle const favoriteTitle = recursiveFindTitle(path, menuStore.favoriteItems) if (favoriteTitle) return favoriteTitle const exampleTitle = recursiveFindTitle(path, menuItemsExample) if (exampleTitle) return exampleTitle if (path === '/') return '首頁' return path } function findMenuItem(path: string) { const recursiveFind = (items: LayoutMenuItem[]): LayoutMenuItem | null => { for (const item of items) { if (item.path === path) return item if (item.subItems?.length) { const found = recursiveFind(item.subItems) if (found) return found } } return null } return recursiveFind(mergedMenuItems.value) } const currentFavoriteInfo = computed(() => { const path = route.path const menuItem = findMenuItem(path) const title = menuItem?.title || (typeof route.meta?.title === 'string' ? route.meta.title : null) || findTitle(path) return { title, path, icon: menuItem?.icon, } }) const isCurrentFavorite = computed(() => favoritesStore.isFavorite(route.path)) const isFavoriteActionDisabled = computed( () => !currentFavoriteInfo.value?.path || route.path === '/' ) const favoriteActionLabel = computed(() => (isCurrentFavorite.value ? '移除常用' : '加入常用')) const favoriteActionIcon = computed(() => isCurrentFavorite.value ? mdiCloseCircle : mdiPlusCircle ) function toggleFavoriteItem(item: LayoutMenuItem) { if (!item?.path || item.path === '/') return favoritesStore.toggle({ title: item.title || findTitle(item.path), path: item.path, icon: item.icon, }) } function toggleFavorite() { toggleFavoriteItem(currentFavoriteInfo.value) } function handleRemoveFavorite(item: LayoutMenuItem) { toggleFavoriteItem(item) } function goHome() { router.push('/') } function updateBreadcrumbs() { const resolvedTitle = findTitle(route.path) const fallbackTitle = resolvedTitle && resolvedTitle !== route.path ? resolvedTitle : typeof route.meta?.title === 'string' ? route.meta.title : null breadcrumbStore.setBreadcrumbs({ path: route.path, menuItems: mergedMenuItems.value, favoriteItems: mergedFavoriteItems.value, fallbackTitle, homeLabel: '首頁', homeIcon: mdiHome, }) } function handleLayoutAction(type: string) { if (type === 'messages') { messageStore.open() } } function performLogout(feedback: { message: string; color: string }) { authStore.logout() options.onLogout?.() snackbar.show(feedback) router.replace({ name: 'login' }) } function handleLogout() { performLogout({ message: '登出成功', color: 'success' }) } function handleForceLogout(event: Event) { const message = (event as CustomEvent)?.detail?.message || '請重新登入' performLogout({ message, color: 'warning' }) } watch( [ () => route.path, () => menuStore.menuItems, () => menuStore.favoriteItems, () => favoritesStore.items, ], () => updateBreadcrumbs(), { immediate: true, deep: true } ) onMounted(() => { window.addEventListener(SESSION_FORCE_LOGOUT_EVENT, handleForceLogout) }) onBeforeUnmount(() => { window.removeEventListener(SESSION_FORCE_LOGOUT_EVENT, handleForceLogout) }) return { favoriteActionIcon, favoriteActionLabel, favoritesStore, goHome, handleLayoutAction, handleLogout, handleRemoveFavorite, handleSelect, isFavoriteActionDisabled, layoutProps, menuStore, mergedMenuItems, toggleFavorite, } }