Files
sb-front/src/components/layout/AppSidebar.vue
T
ByungCheol 4ffc01f484 feat: 둘러보기(데모)를 실제 화면 그대로 — 데모 모드 + API 더미 응답
- src/demo: demo.on 플래그 + DTO 형태 더미데이터 + mock API(읽기 더미/쓰기 차단)
- accountApi: Proxy 로 데모 시 읽기→더미, 쓰기→안내. 실제 뷰 그대로 렌더
- App: authed=인증||데모 → 실제 사이드바/셸 표시 + 데모 배너(로그인/나가기)
- 라우터: 데모 허용(가계부/고정/계좌/분류) 외 보호화면은 DemoLockedView(로그인 유도)
- 사이드바: 데모 시 잠금(🔒) 배지, 메뉴는 showMenu(인증||데모)
- 로그인/로그아웃 시 데모 자동 해제, 랜딩 '둘러보기'→enterDemo
- 기존 독립 DemoView 제거

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 07:49:37 +09:00

204 lines
8.4 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>
import { computed } from 'vue'
import { RouterLink } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import { useUiStore } from '@/stores/ui'
import { BOARDS } from '@/constants/boards'
import { demo } from '@/demo'
const auth = useAuthStore()
const ui = useUiStore()
// 로그인 또는 둘러보기(데모)면 메뉴 노출. 데모에선 잠금 배지를 함께 표시.
const showMenu = computed(() => auth.isAuthenticated || demo.on)
// 메뉴 아이콘 (lucide 스타일 인라인 SVG path — 하단 내비와 동일 톤). 값은 정적/신뢰 마크업.
const icons = {
entries: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>',
stats: '<line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/>',
recurrings: '<polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/>',
wallets: '<rect x="1" y="4" width="22" height="16" rx="2" ry="2"/><line x1="1" y1="10" x2="23" y2="10"/>',
categories: '<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>',
budget: '<path d="M21.21 15.89A10 10 0 1 1 8 2.83"/><path d="M22 12A10 10 0 0 0 12 2v10z"/>',
tags: '<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/>',
board: '<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>',
users: '<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
sliders: '<line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/><line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/><line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/><line x1="1" y1="14" x2="7" y2="14"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="17" y1="16" x2="23" y2="16"/>',
}
</script>
<template>
<aside class="app-sidebar">
<div class="drawer-head">
<RouterLink to="/" class="brand" @click="ui.closeSidebar()">
<img class="brand-mark" src="/logo-piggy.png" alt="" aria-hidden="true" />
<span class="brand-text">돈돼지 가계부</span>
</RouterLink>
<button class="drawer-close" type="button" aria-label="닫기" @click="ui.closeSidebar()">×</button>
</div>
<nav class="menu">
<!-- 가계부 영역 (홈은 하단 내비게이션으로 이동) -->
<template v-if="showMenu">
<RouterLink to="/account/entries" class="menu-item">
<svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" v-html="icons.entries" />
<span>가계부 내역</span>
</RouterLink>
<RouterLink to="/account/stats" class="menu-item">
<svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" v-html="icons.stats" />
<span>통계</span>
<span v-if="demo.on" class="lock-badge">🔒</span>
</RouterLink>
<RouterLink to="/account/recurrings" class="menu-item">
<svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" v-html="icons.recurrings" />
<span>고정 지출</span>
</RouterLink>
<RouterLink to="/account/wallets" class="menu-item">
<svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" v-html="icons.wallets" />
<span>계좌 관리</span>
</RouterLink>
<RouterLink to="/account/categories" class="menu-item">
<svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" v-html="icons.categories" />
<span>분류 관리</span>
</RouterLink>
<RouterLink to="/account/budget" class="menu-item">
<svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" v-html="icons.budget" />
<span>예산 설정</span>
<span v-if="demo.on" class="lock-badge">🔒</span>
</RouterLink>
<RouterLink to="/account/tags" class="menu-item">
<svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" v-html="icons.tags" />
<span>태그 관리</span>
<span v-if="demo.on" class="lock-badge">🔒</span>
</RouterLink>
<!-- 게시판 영역 -->
<hr class="menu-divider" />
<RouterLink
v-for="b in BOARDS"
:key="b.key"
:to="`/board/${b.key}`"
class="menu-item"
>
<svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" v-html="icons.board" />
<span>{{ b.label }}</span>
<span v-if="demo.on" class="lock-badge">🔒</span>
</RouterLink>
</template>
<!-- 관리자 영역 -->
<template v-if="auth.user?.role === 'ADMIN'">
<hr class="menu-divider" />
<RouterLink to="/users" class="menu-item">
<svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" v-html="icons.users" />
<span>회원 관리</span>
</RouterLink>
<RouterLink to="/admin/tags" class="menu-item">
<svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" v-html="icons.tags" />
<span>태그 관리(관리자)</span>
</RouterLink>
<RouterLink to="/admin/default-categories" class="menu-item">
<svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" v-html="icons.sliders" />
<span>기본 분류 설정</span>
</RouterLink>
</template>
</nav>
</aside>
</template>
<style scoped>
.app-sidebar {
background: var(--color-background-soft);
border-right: 1px solid var(--color-border);
padding: 0 0 1rem;
}
.drawer-head {
display: flex;
align-items: center;
justify-content: space-between;
height: 56px; /* 헤더 높이와 맞춰 상단 정렬 (하단 보더 일치) */
padding: 0 1.1rem;
margin-bottom: 0.5rem;
border-bottom: 1px solid var(--color-border);
}
.brand {
display: inline-flex;
align-items: center;
gap: 0.45rem;
font-size: 1.15rem;
font-weight: 700;
color: var(--color-heading);
}
.brand:hover {
background: transparent;
}
.brand-mark {
height: 26px;
width: auto;
max-width: 34px;
object-fit: contain;
flex-shrink: 0;
}
.brand-text {
letter-spacing: -0.01em;
white-space: nowrap;
}
.drawer-close {
display: none; /* 데스크톱: 숨김 (모바일 드로어에서만 노출) */
border: 0;
background: transparent;
color: var(--color-text);
font-size: 1.6rem;
line-height: 1;
cursor: pointer;
}
@media (max-width: 768px) {
.app-sidebar {
background: var(--color-background);
min-height: 100%;
}
.drawer-close {
display: inline-flex;
}
}
.menu {
display: flex;
flex-direction: column;
}
.menu-item {
display: flex;
align-items: center;
gap: 0.7rem;
padding: 0.7rem 1.5rem;
color: var(--color-text);
font-size: 0.95rem;
}
.menu-icon {
width: 18px;
height: 18px;
flex: none;
opacity: 0.8;
}
.lock-badge {
margin-left: auto;
font-size: 0.72rem;
opacity: 0.55;
}
.menu-item.router-link-exact-active .menu-icon {
opacity: 1;
}
.menu-divider {
border: 0;
border-top: 1px solid var(--color-border);
margin: 0.5rem 1.5rem;
}
.menu-item:hover {
background: var(--color-background-mute);
}
.menu-item.router-link-exact-active {
color: hsla(160, 100%, 37%, 1);
font-weight: 600;
border-left: 3px solid hsla(160, 100%, 37%, 1);
padding-left: calc(1.5rem - 3px);
background: transparent;
}
</style>