Files
bio_frontend/layouts/default.vue
2025-08-14 11:00:48 +09:00

205 lines
5.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import AppHeader from "../components/AppHeader.vue";
import { ref, computed, watch, onMounted, onBeforeUnmount } from "vue";
import { useRouter, useRoute } from "vue-router";
import { useTabsStore } from "../stores/tab";
const router = useRouter();
const route = useRoute();
const activeMenu = ref("home");
const showSubmenuBar = ref(false);
const tabsStore = useTabsStore();
// HOME 메뉴가 선택되었을 때 최상단 경로로 이동
watch(activeMenu, (newValue) => {
if (newValue === "home") {
router.push("/");
}
});
watch(route, () => {
showSubmenuBar.value = false;
});
// 서브메뉴 정의
const subMenus = computed(() => {
if (activeMenu.value === "test") {
return [
{ key: "test", label: "테스트", to: "/test/test01" },
{ key: "igv", label: "ivg", to: "/test/test02" },
{ key: "igv2", label: "ivg2", to: "/test/igv2" },
{ key: "pathway", label: "pathway", to: "/test/pathway" },
{ key: "pathway2", label: "pathway2", to: "/test/pathway2" },
{ key: "pathway3", label: "pathway3", to: "/test/pathway3" },
{ key: "pathway4", label: "pathway4", to: "/cultureGraph/pathway4" },
{ key: "pathwayjson", label: "pathwayjson", to: "/test/pathwayjson" },
{ key: "cultureGraph", label: "배양그래프", to: "/test/culture-graph" },
{ key: "cultureGraphMulti", label: "배양그래프 멀티", to: "/test/culture-graph-multi" },
{ key: "cultureGraphTab", label: "배양그래프 탭", to: "/test/culture-graph-tab" },
{ key: "tui-grid", label: "tui-grid", to: "/tui" },
{ key: "리소스", label: "리소스", to: "/admin/resource" },
];
} else if (activeMenu.value === "admin") {
return [
{ key: "logs", label: "접속기록", to: "/admin/logs" },
{ key: "codes", label: "공통코드", to: "/admin/codes" },
{ key: "programs", label: "프로그램", to: "/admin/programs" },
];
}
return [];
});
function onMenuClick(menu: string) {
activeMenu.value = menu;
showSubmenuBar.value = true;
}
// 서브메뉴 클릭 시 탭 추가
function onSubMenuClick(sub: { key: string; label: string; to: string, componentName:string }) {
tabsStore.addTab(sub);
router.push(sub.to);
}
function handleClickOutsideSubmenuBar(event: MouseEvent) {
const submenu = document.querySelector(".submenu-bar");
if (
submenu &&
!submenu.contains(event.target as Node) &&
!(event.target as HTMLElement).classList.contains("menu-btn")
) {
showSubmenuBar.value = false;
}
}
onMounted(() => {
window.addEventListener("click", handleClickOutsideSubmenuBar);
});
onBeforeUnmount(() => {
window.removeEventListener("click", handleClickOutsideSubmenuBar);
});
</script>
<template>
<div class="layout">
<AppHeader v-model="activeMenu" @update:model-value="onMenuClick" />
<!-- 서브메뉴 -->
<nav
v-if="subMenus && subMenus.length && showSubmenuBar"
class="submenu-bar"
@click.stop
>
<button
v-for="sub in subMenus"
:key="sub.key"
class="submenu-btn"
:class="{ active: $route.path === sub.to }"
@click="onSubMenuClick({...sub, componentName : sub.key})"
>
{{ sub.label }}
</button>
</nav>
<!-- 동적 -->
<div v-if="tabsStore.tabs.length" class="tab-bar">
<div
v-for="tab in tabsStore.tabs"
:key="tab.key"
class="tab-item"
:class="{ active: tabsStore.activeTab === tab.key }"
@click="tabsStore.setActiveTab(tab.key); router.push(tab.to)"
>
{{ tab.label }}
<span class="close-btn" @click.stop="tabsStore.removeTab(tab.key)">×</span>
</div>
</div>
<main class="main">
<slot />
</main>
<footer class="footer">
<p>&copy; 2024 Nuxt.js App</p>
</footer>
</div>
</template>
<style scoped>
.layout {
min-height: 100vh;
display: flex;
flex-direction: column;
position: relative;
}
.main {
flex: 1;
padding: 2rem;
padding-top: 0.5rem;
}
.footer {
background: #f8f9fa;
padding: 1rem;
text-align: center;
border-top: 1px solid #e9ecef;
}
.submenu-bar {
background: #f4f6fa;
border-bottom: 1px solid #e0e7ef;
padding: 0.5rem 2rem;
display: flex;
gap: 1rem;
position: absolute;
top: 80px;
left: 0;
right: 0;
z-index: 10;
}
.submenu-btn {
font-size: 1.05rem;
font-weight: 500;
color: #222;
background: none;
border: none;
padding: 0.5rem 1.2rem;
border-radius: 6px;
transition:
background 0.15s,
color 0.15s;
cursor: pointer;
}
.submenu-btn.active {
background: none;
color: #1976d2;
}
.submenu-btn:hover {
background: #e6f0fa;
color: #1976d2;
}
/* 탭바 스타일 */
.tab-bar {
display: flex;
gap: 6px;
padding: 0.4rem 0.8rem;
background: #fff;
border-bottom: 1px solid #ddd;
}
.tab-item {
padding: 0.3rem 0.8rem;
background: #f2f2f2;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
}
.tab-item.active {
background: #1976d2;
color: white;
}
.close-btn {
margin-left: 6px;
cursor: pointer;
}
</style>