public 기능 완료
This commit is contained in:
+108
-93
@@ -1,100 +1,103 @@
|
||||
<template>
|
||||
<div class="app">
|
||||
<AppHeader />
|
||||
|
||||
<div class="app-content">
|
||||
<!-- 사이드바 오버레이 추가 -->
|
||||
<div v-if="isSidebarVisible && isMobileView" class="sidebar-overlay" @click="closeSidebar"></div>
|
||||
|
||||
<!-- 사이드바에 모바일 클래스 추가 -->
|
||||
<aside v-if="isLoggedIn" class="sidebar" :class="{ 'mobile-visible': isSidebarVisible, 'mobile-hidden': !isSidebarVisible && isMobileView }">
|
||||
<div class="sidebar-content">
|
||||
<!-- 클럽 정보 -->
|
||||
<div class="club-info" v-if="selectedClub">
|
||||
<div class="club-header">
|
||||
<h3>{{ selectedClub.name }}</h3>
|
||||
<p class="club-location"><i class="pi pi-map-marker"></i> {{ selectedClub.location }}</p>
|
||||
</div>
|
||||
<div class="club-stats">
|
||||
<div class="stat-item">
|
||||
<i class="pi pi-users"></i>
|
||||
<span>{{ memberCount }} 명</span>
|
||||
<template v-if="isRouterReady">
|
||||
<AppPublicLayout v-if="isPublicLayout">
|
||||
<router-view />
|
||||
</AppPublicLayout>
|
||||
<div v-else class="app">
|
||||
<AppHeader />
|
||||
<div class="app-content">
|
||||
<div v-if="isSidebarVisible && isMobileView" class="sidebar-overlay" @click="closeSidebar"></div>
|
||||
<aside v-if="isLoggedIn" class="sidebar" :class="{ 'mobile-visible': isSidebarVisible, 'mobile-hidden': !isSidebarVisible && isMobileView }">
|
||||
<div class="sidebar-content">
|
||||
<!-- 클럽 정보 -->
|
||||
<div class="club-info" v-if="selectedClub">
|
||||
<div class="club-header">
|
||||
<h3>{{ selectedClub.name }}</h3>
|
||||
<p class="club-location"><i class="pi pi-map-marker"></i> {{ selectedClub.location }}</p>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<i class="pi pi-user"></i>
|
||||
<span>모임장: {{ ownerName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 클럽 선택 기능 (관리자 또는 클럽이 2개 이상) -->
|
||||
<div v-if="isAdmin || clubs.length > 1" class="club-selector">
|
||||
<label for="club-select">클럽 선택:</label>
|
||||
<select id="club-select" v-model="selectedClubId" @change="onClubChange">
|
||||
<option v-for="club in clubs" :key="club.id" :value="club.id">{{ club.name }}({{ club.memberType }})</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 커스텀 사이드바 메뉴 -->
|
||||
<div class="custom-sidebar-menu">
|
||||
<template v-for="(menuGroup, groupIndex) in menuItems" :key="groupIndex">
|
||||
<div class="menu-group">
|
||||
<div class="menu-group-header" @click="toggleMenuGroup(groupIndex)">
|
||||
<i :class="menuGroup.icon"></i>
|
||||
<span>{{ menuGroup.label }}</span>
|
||||
<i class="pi" :class="expandedGroups[groupIndex] ? 'pi-chevron-down' : 'pi-chevron-right'"></i>
|
||||
<div class="club-stats">
|
||||
<div class="stat-item">
|
||||
<i class="pi pi-users"></i>
|
||||
<span>{{ memberCount }} 명</span>
|
||||
</div>
|
||||
|
||||
<div class="menu-items" v-if="expandedGroups[groupIndex]" v-show="menuGroup.items && menuGroup.items.length">
|
||||
<router-link
|
||||
v-for="(item, itemIndex) in menuGroup.items"
|
||||
:key="itemIndex"
|
||||
:to="item.to"
|
||||
class="menu-item"
|
||||
exact
|
||||
active-class="router-link-active"
|
||||
@click="closeSidebarOnMobile"
|
||||
>
|
||||
<i :class="item.icon"></i>
|
||||
<span>{{ item.label }}</span>
|
||||
</router-link>
|
||||
<div class="stat-item">
|
||||
<i class="pi pi-user"></i>
|
||||
<span>모임장: {{ ownerName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 그룹이 없는 단일 메뉴 아이템 -->
|
||||
<template v-for="(menuItem, itemIndex) in singleMenuItems" :key="'single-'+itemIndex">
|
||||
<router-link
|
||||
:to="menuItem.to"
|
||||
class="menu-item single-menu-item"
|
||||
exact
|
||||
active-class="router-link-active"
|
||||
@click="closeSidebarOnMobile"
|
||||
>
|
||||
<i :class="menuItem.icon"></i>
|
||||
<span>{{ menuItem.label }}</span>
|
||||
</router-link>
|
||||
</template>
|
||||
</div>
|
||||
<!-- 클럽 선택 기능 (관리자 또는 클럽이 2개 이상) -->
|
||||
<div v-if="isAdmin || clubs.length > 1" class="club-selector">
|
||||
<label for="club-select">클럽 선택:</label>
|
||||
<select id="club-select" v-model="selectedClubId" @change="onClubChange">
|
||||
<option v-for="club in clubs" :key="club.id" :value="club.id">{{ club.name }}({{ club.memberType }})</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- 커스텀 사이드바 메뉴 -->
|
||||
<div class="custom-sidebar-menu">
|
||||
<template v-for="(menuGroup, groupIndex) in menuItems" :key="groupIndex">
|
||||
<div class="menu-group">
|
||||
<div class="menu-group-header" @click="toggleMenuGroup(groupIndex)">
|
||||
<i :class="menuGroup.icon"></i>
|
||||
<span>{{ menuGroup.label }}</span>
|
||||
<i class="pi" :class="expandedGroups[groupIndex] ? 'pi-chevron-down' : 'pi-chevron-right'"></i>
|
||||
</div>
|
||||
<div class="menu-items" v-if="expandedGroups[groupIndex]" v-show="menuGroup.items && menuGroup.items.length">
|
||||
<router-link
|
||||
v-for="(item, itemIndex) in menuGroup.items"
|
||||
:key="itemIndex"
|
||||
:to="item.to"
|
||||
class="menu-item"
|
||||
exact
|
||||
active-class="router-link-active"
|
||||
@click="closeSidebarOnMobile"
|
||||
>
|
||||
<i :class="item.icon"></i>
|
||||
<span>{{ item.label }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 그룹이 없는 단일 메뉴 아이템 -->
|
||||
<template v-for="(menuItem, itemIndex) in singleMenuItems" :key="'single-'+itemIndex">
|
||||
<router-link
|
||||
:to="menuItem.to"
|
||||
class="menu-item single-menu-item"
|
||||
exact
|
||||
active-class="router-link-active"
|
||||
@click="closeSidebarOnMobile"
|
||||
>
|
||||
<i :class="menuItem.icon"></i>
|
||||
<span>{{ menuItem.label }}</span>
|
||||
</router-link>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="main-content" :class="{ 'full-width': !isLoggedIn }">
|
||||
<router-view />
|
||||
</main>
|
||||
</aside>
|
||||
<main class="main-content" :class="{ 'full-width': !isLoggedIn }">
|
||||
<router-view />
|
||||
</main>
|
||||
</div>
|
||||
<footer>
|
||||
<p>© {{ new Date().getFullYear() }} 볼링 클럽 매니저. All rights reserved.</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>© {{ new Date().getFullYear() }} 볼링 클럽 매니저. All rights reserved.</p>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onBeforeUnmount, provide } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import AppHeader from './components/AppHeader.vue';
|
||||
import AppPublicLayout from './layouts/AppPublicLayout.vue';
|
||||
import apiClient from './services/api';
|
||||
|
||||
const route = useRoute();
|
||||
const layoutComponent = computed(() => route.meta.layout === 'public' ? AppPublicLayout : 'div');
|
||||
const isPublicLayout = computed(() => {
|
||||
// 라우트 meta.layout이 public이거나, 경로가 /public/로 시작하면 public 레이아웃으로 간주
|
||||
return route.meta.layout === 'public' || route.path.startsWith('/public/');
|
||||
});
|
||||
import authService from './services/authService';
|
||||
|
||||
// 상태 변수
|
||||
@@ -111,7 +114,7 @@ const selectedClubId = ref(localStorage.getItem('clubId') || null); // 선택된
|
||||
const selectedClub = ref(null);
|
||||
const memberCount = ref(0);
|
||||
const ownerName = ref('');
|
||||
|
||||
const isRouterReady = ref(false);
|
||||
const userRole = computed(() => user.value?.role || '');
|
||||
const isAdmin = computed(() => userRole.value === 'admin' || userRole.value === 'superadmin');
|
||||
|
||||
@@ -156,6 +159,7 @@ const initializeExpandedGroups = () => {
|
||||
|
||||
// 클럽 목록 가져오기
|
||||
const fetchClubs = async () => {
|
||||
if (isPublicLayout.value) return;
|
||||
try {
|
||||
const userId = user.value?.id;
|
||||
const userRole = localStorage.getItem('userRole');
|
||||
@@ -182,6 +186,7 @@ const fetchClubs = async () => {
|
||||
|
||||
// 클럽 정보 가져오기
|
||||
const fetchClubInfo = async () => {
|
||||
if (isPublicLayout.value) return;
|
||||
try {
|
||||
const response = await apiClient.post(`/api/club`, { clubId: selectedClubId.value ?? clubs.value[0].id });
|
||||
|
||||
@@ -201,6 +206,7 @@ const fetchClubInfo = async () => {
|
||||
|
||||
// 클럽 선택
|
||||
const setSelectedClub = async (club) => {
|
||||
if (isPublicLayout.value) return;
|
||||
// 1. localStorage에 저장
|
||||
localStorage.setItem('clubId', club.id);
|
||||
localStorage.setItem('userClubRole', club.memberType || '');
|
||||
@@ -237,6 +243,7 @@ const onClubChange = async () => {
|
||||
|
||||
// 메뉴 아이템 로드
|
||||
const loadMenuItems = async () => {
|
||||
if (isPublicLayout.value) return;
|
||||
try {
|
||||
const role = localStorage.getItem('userRole');
|
||||
const clubId = selectedClubId.value;
|
||||
@@ -324,10 +331,19 @@ const handleUserLoggedIn = async () => {
|
||||
|
||||
// 컴포넌트 마운트 시 데이터 로드
|
||||
onMounted(async () => {
|
||||
await router.isReady?.()
|
||||
isRouterReady.value = true
|
||||
// public 레이아웃이면 내부 데이터 로딩/초기화 완전 스킵
|
||||
if (isPublicLayout.value) {
|
||||
window.addEventListener('resize', handleResize);
|
||||
window.addEventListener('user-logged-in', handleUserLoggedIn);
|
||||
initializeExpandedGroups();
|
||||
return;
|
||||
}
|
||||
// 내부(클럽) 페이지만 내부 데이터 로딩
|
||||
await loadUserData();
|
||||
const clubId = localStorage.getItem('clubId');
|
||||
if (clubId) {
|
||||
// 서버 세션에 clubId를 동기화
|
||||
apiClient.post('/api/club/select-club', { clubId });
|
||||
}
|
||||
if (isLoggedIn.value) {
|
||||
@@ -335,14 +351,8 @@ onMounted(async () => {
|
||||
await fetchClubInfo();
|
||||
loadMenuItems();
|
||||
}
|
||||
|
||||
// 화면 크기 변경 감지
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
// 로그인 성공 이벤트 감지
|
||||
window.addEventListener('user-logged-in', handleUserLoggedIn);
|
||||
|
||||
// 초기 expandedGroups 설정
|
||||
initializeExpandedGroups();
|
||||
});
|
||||
|
||||
@@ -352,7 +362,12 @@ onBeforeUnmount(() => {
|
||||
window.removeEventListener('user-logged-in', handleUserLoggedIn);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
<style scoped>
|
||||
/* App.vue 스타일 */
|
||||
.app {
|
||||
|
||||
Reference in New Issue
Block a user