Files
sb-front/src/components/layout/AppSidebar.vue
T
ByungCheol 75228cbb64 feat: 사이드바 브랜드/헤더 타이틀 + 라이트·다크 테마 설정
1) 사이드바 상단에 돼지 로고 + '돈돼지 가계부'('메뉴' 텍스트 대체, 데스크톱/모바일 공통)
2) 헤더에서 돼지 로고 제거 → 현재 화면 타이틀 표시(라우트별)
3) 설정에 화면 테마(시스템/라이트/다크) 선택 추가
   - base.css: data-theme 기반 수동 다크 + 시스템 설정 폴백
   - theme.js 유틸 + ui 스토어 theme 상태, main.js 시작 시 적용(깜빡임 방지)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 22:15:27 +09:00

190 lines
7.8 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 { RouterLink } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import { useUiStore } from '@/stores/ui'
import { BOARDS } from '@/constants/boards'
const auth = useAuthStore()
const ui = useUiStore()
// 메뉴 아이콘 (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="auth.isAuthenticated">
<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>
</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>
</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>
</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>
</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: 1rem 0;
}
.drawer-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.5rem 1.1rem 0.75rem;
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;
}
.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>