@@ -0,0 +1,470 @@
< script setup >
// 로그인 없이 둘러보기 — 자체 메뉴로 가계부/고정지출/계좌/분류를 샘플(더미) 데이터로 보여준다.
// 나머지 메뉴는 로그인 유도 안내. API 호출/실데이터 없음.
import { ref , computed } from 'vue'
import { useUiStore } from '@/stores/ui'
const ui = useUiStore ( )
const MENU = [
{ key : 'account' , label : '가계부 내역' } ,
{ key : 'stats' , label : '통계' , locked : true } ,
{ key : 'recurrings' , label : '고정 지출' } ,
{ key : 'wallets' , label : '계좌 관리' } ,
{ key : 'categories' , label : '분류 관리' } ,
{ key : 'budget' , label : '예산 설정' , locked : true } ,
{ key : 'tags' , label : '태그 관리' , locked : true } ,
{ key : 'board' , label : '커뮤니티' , locked : true } ,
]
const active = ref ( 'account' )
const activeItem = computed ( ( ) => MENU . find ( ( m ) => m . key === active . value ) || MENU [ 0 ] )
// ===== 더미 데이터 =====
const summary = { income : 4600000 , expense : 1827600 , balance : 2772400 }
const accountGroups = [
{ date : '6월 27일 (금)' , total : - 42300 , items : [
{ cat : '식비' , memo : '점심 · 김밥천국' , type : 'EXPENSE' , amount : 9000 } ,
{ cat : '카페/간식' , memo : '스타벅스' , type : 'EXPENSE' , amount : 5300 } ,
{ cat : '교통' , memo : '지하철' , type : 'EXPENSE' , amount : 2800 } ,
{ cat : '쇼핑' , memo : '다이소' , type : 'EXPENSE' , amount : 25200 } ,
] } ,
{ date : '6월 25일 (수)' , total : 4554700 , items : [
{ cat : '급여' , memo : '6월 급여' , type : 'INCOME' , amount : 4600000 } ,
{ cat : '통신' , memo : '휴대폰 요금' , type : 'EXPENSE' , amount : 45300 } ,
] } ,
{ date : '6월 23일 (월)' , total : - 68000 , items : [
{ cat : 'OTT/구독' , memo : '넷플릭스' , type : 'EXPENSE' , amount : 17000 } ,
{ cat : '식비' , memo : '저녁 · 배달' , type : 'EXPENSE' , amount : 23000 } ,
{ cat : '생활' , memo : '마트 장보기' , type : 'EXPENSE' , amount : 28000 } ,
] } ,
]
const recurrings = [
{ title : '월세' , freq : '매월 1일' , cat : '주거' , type : 'EXPENSE' , amount : 600000 } ,
{ title : '통신비' , freq : '매월 15일' , cat : '통신' , type : 'EXPENSE' , amount : 45300 } ,
{ title : '넷플릭스' , freq : '매월 23일' , cat : 'OTT/구독' , type : 'EXPENSE' , amount : 17000 } ,
{ title : '정기적금' , freq : '매월 25일' , cat : '저축' , type : 'TRANSFER' , amount : 300000 } ,
{ title : '급여' , freq : '매월 25일' , cat : '급여' , type : 'INCOME' , amount : 4600000 } ,
]
const wallets = {
total : 15520000 ,
items : [
{ name : '신한 주거래' , kind : '은행' , amount : 2540000 } ,
{ name : '주택청약' , kind : '은행' , amount : 5000000 } ,
{ name : '현금' , kind : '현금' , amount : 120000 } ,
{ name : '삼성카드' , kind : '카드' , amount : - 340000 } ,
{ name : '연금저축(투자)' , kind : '투자' , amount : 8200000 } ,
] ,
}
const categories = [
{ major : '식비' , subs : [ '점심' , '저녁' , '카페/간식' , '배달' ] } ,
{ major : '교통' , subs : [ '대중교통' , '택시' , '주유' ] } ,
{ major : '주거/통신' , subs : [ '월세' , '관리비' , '통신비' ] } ,
{ major : '문화/여가' , subs : [ 'OTT/구독' , '영화' , '여행' ] } ,
{ major : '쇼핑' , subs : [ '생활' , '의류' , '온라인' ] } ,
]
function won ( n ) {
return ( n < 0 ? '-' : '' ) + Math . abs ( n ) . toLocaleString ( 'ko-KR' )
}
function typeLabel ( t ) {
return t === 'INCOME' ? '수입' : t === 'TRANSFER' ? '이체' : '지출'
}
function goLogin ( ) {
ui . openLogin ( '/account' )
}
< / script >
< template >
< section class = "demo" >
< div class = "banner" >
< span > 👀 < b > 둘러보기 모드 < / b > · 아래는 샘플 데이터입니다 . < / span >
< button type = "button" class = "login-btn" @click ="goLogin" > 로그인하고 시작 < / button >
< / div >
< div class = "demo-shell" >
<!-- 데모 메뉴 -- >
< nav class = "demo-menu" >
< button
v-for = "m in MENU" :key="m.key" type="button"
class = "dm-item" : class = "{ active: active === m.key }"
@click ="active = m.key"
>
< span > { { m . label } } < / span >
< span v-if = "m.locked" class="lock" > 🔒 < / span >
< / button >
< / nav >
< ! - - 콘텐츠 - - >
< div class = "demo-content" >
<!-- 가계부 내역 -- >
< template v-if = "active === 'account'" >
< div class = "month-nav" > < span class = "chev" > ‹ < / span > < span class = "period" > 2026 년 6 월 < / span > < span class = "chev" > › < / span > < / div >
< div class = "summary" >
< div class = "s-card" > < span class = "s-label" > 수입 < / span > < span class = "s-val income" > { { won ( summary . income ) } } < / span > < / div >
< div class = "s-card" > < span class = "s-label" > 지출 < / span > < span class = "s-val expense" > { { won ( summary . expense ) } } < / span > < / div >
< div class = "s-card" > < span class = "s-label" > 잔액 < / span > < span class = "s-val" > { { won ( summary . balance ) } } < / span > < / div >
< / div >
< div v-for = "g in accountGroups" :key="g.date" class="card-group" >
< div class = "cg-head" >
< span class = "cg-title" > { { g . date } } < / span >
< span class = "cg-total" : class = "g.total >= 0 ? 'income' : 'expense'" > { { won ( g . total ) } } < / span >
< / div >
< ul class = "rows" >
< li v-for = "(e, i) in g.items" :key="i" class="row3" >
< span class = "tag" > { { e . cat } } < / span >
< span class = "ellip" > { { e . memo } } < / span >
< span class = "amt" : class = "e.type === 'INCOME' ? 'income' : 'expense'" > { { e . type === 'INCOME' ? '+' : '-' } } { { won ( e . amount ) } } < / span >
< / li >
< / ul >
< / div >
< / template >
<!-- 고정 지출 -- >
< template v-else-if = "active === 'recurrings'" >
< p class = "hint" > 등록한 주기에 맞춰 가계부에 자동으로 '확인 필요' 내역이 생성됩니다 . < / p >
< div class = "card-group" >
< ul class = "rows" >
< li v-for = "(r, i) in recurrings" :key="i" class="row-rec" >
< div class = "rec-main" >
< span class = "rec-title" > { { r . title } } < / span >
< span class = "rec-sub" > < span class = "tag" > { { r . cat } } < / span > · { { r . freq } } · { { typeLabel ( r . type ) } } < / span >
< / div >
< span class = "amt" : class = "r.type === 'INCOME' ? 'income' : r.type === 'TRANSFER' ? '' : 'expense'" > { { won ( r . amount ) } } < / span >
< / li >
< / ul >
< / div >
< / template >
<!-- 계좌 관리 -- >
< template v-else-if = "active === 'wallets'" >
< div class = "networth" >
< span class = "nw-label" > 총자산 < / span >
< span class = "nw-val income" > { { won ( wallets . total ) } } < / span >
< / div >
< div class = "card-group" >
< ul class = "rows" >
< li v-for = "(w, i) in wallets.items" :key="i" class="row-rec" >
< div class = "rec-main" >
< span class = "rec-title" > { { w . name } } < / span >
< span class = "rec-sub" > < span class = "tag" > { { w . kind } } < / span > < / span >
< / div >
< span class = "amt" : class = "w.amount < 0 ? 'expense' : ''" > { { won ( w . amount ) } } < / span >
< / li >
< / ul >
< / div >
< / template >
<!-- 분류 관리 -- >
< template v-else-if = "active === 'categories'" >
< p class = "hint" > 대분류 아래 소분류로 묶어 관리합니다 . ( 예산 · 통계는 소분류 기준 ) < / p >
< div v-for = "(c, i) in categories" :key="i" class="card-group" >
< div class = "cg-head" > < span class = "major-badge" > 대 < / span > < span class = "cg-title" > { { c . major } } < / span > < / div >
< ul class = "rows" >
< li v-for = "(s, j) in c.subs" :key="j" class="row-sub2"><span class="sub-mark" > ↳ < / span > {{ s }} < / li >
< / ul >
< / div >
< / template >
< ! - - 잠금 ( 로그인 유도 ) - - >
< div v-else class = "locked-box" >
< div class = "lock-big" > 🔒 < / div >
< p class = "lock-title" > { { activeItem . label } } 는 로그인 후 이용할 수 있어요 < / p >
< p class = "lock-desc" > 로그인하면 통계 · 예산 · 태그 · 게시판까지 모두 사용할 수 있습니다 . < / p >
< button type = "button" class = "cta-btn" @click ="goLogin" > 로그인 / 시작하기 < / button >
< / div >
< / div >
< / div >
< div class = "cta" >
< p > 마음에 드시나요 ? 로그인하면 < b > 내 가계부 < / b > 를 바로 시작할 수 있어요 . < / p >
< button type = "button" class = "cta-btn" @click ="goLogin" > 로그인 / 시작하기 < / button >
< / div >
< / section >
< / template >
< style scoped >
. demo {
padding - bottom : 1 rem ;
}
. banner {
display : flex ;
align - items : center ;
justify - content : space - between ;
gap : 0.75 rem ;
flex - wrap : wrap ;
padding : 0.7 rem 0.9 rem ;
margin - bottom : 1 rem ;
border : 1 px solid hsla ( 160 , 100 % , 37 % , 0.4 ) ;
border - radius : 8 px ;
background : hsla ( 160 , 100 % , 37 % , 0.08 ) ;
font - size : 0.9 rem ;
}
. login - btn ,
. cta - btn {
padding : 0.45 rem 0.9 rem ;
border : 1 px solid hsla ( 160 , 100 % , 37 % , 1 ) ;
border - radius : 6 px ;
background : hsla ( 160 , 100 % , 37 % , 1 ) ;
color : # fff ;
font - weight : 600 ;
cursor : pointer ;
white - space : nowrap ;
}
. demo - shell {
display : flex ;
gap : 1 rem ;
align - items : flex - start ;
}
. demo - menu {
flex : none ;
width : 150 px ;
display : flex ;
flex - direction : column ;
gap : 0.15 rem ;
border : 1 px solid var ( -- color - border ) ;
border - radius : 10 px ;
padding : 0.4 rem ;
background : var ( -- color - background - soft ) ;
}
. dm - item {
display : flex ;
align - items : center ;
justify - content : space - between ;
gap : 0.3 rem ;
padding : 0.55 rem 0.6 rem ;
border : 0 ;
border - radius : 6 px ;
background : transparent ;
color : var ( -- color - text ) ;
font - size : 0.9 rem ;
text - align : left ;
cursor : pointer ;
}
. dm - item : hover {
background : var ( -- color - background - mute ) ;
}
. dm - item . active {
background : hsla ( 160 , 100 % , 37 % , 0.12 ) ;
color : hsla ( 160 , 100 % , 37 % , 1 ) ;
font - weight : 600 ;
}
. lock {
font - size : 0.75 rem ;
opacity : 0.6 ;
}
. demo - content {
flex : 1 ;
min - width : 0 ;
}
. month - nav {
display : flex ;
align - items : center ;
justify - content : center ;
gap : 1 rem ;
margin - bottom : 1 rem ;
}
. chev {
font - size : 1.4 rem ;
opacity : 0.5 ;
}
. period {
font - size : 1.1 rem ;
font - weight : 600 ;
}
. summary {
display : grid ;
grid - template - columns : repeat ( 3 , 1 fr ) ;
gap : 0.6 rem ;
margin - bottom : 1.25 rem ;
}
. s - card {
display : flex ;
flex - direction : column ;
gap : 0.25 rem ;
padding : 0.7 rem 0.8 rem ;
border : 1 px solid var ( -- color - border ) ;
border - radius : 10 px ;
background : var ( -- color - background - soft ) ;
}
. s - label {
font - size : 0.78 rem ;
opacity : 0.6 ;
}
. s - val {
font - size : 1 rem ;
font - weight : 700 ;
}
. income {
color : hsla ( 160 , 100 % , 37 % , 1 ) ;
}
. expense {
color : # c0392b ;
}
. hint {
font - size : 0.85 rem ;
opacity : 0.7 ;
margin - bottom : 0.8 rem ;
}
. networth {
display : flex ;
align - items : center ;
justify - content : space - between ;
padding : 1 rem ;
margin - bottom : 1 rem ;
border : 1 px solid var ( -- color - border ) ;
border - radius : 10 px ;
background : var ( -- color - background - soft ) ;
}
. nw - label {
font - size : 0.85 rem ;
opacity : 0.65 ;
}
. nw - val {
font - size : 1.3 rem ;
font - weight : 800 ;
}
. card - group {
margin - bottom : 1 rem ;
border : 1 px solid var ( -- color - border ) ;
border - radius : 10 px ;
overflow : hidden ;
}
. cg - head {
display : flex ;
align - items : center ;
gap : 0.5 rem ;
justify - content : space - between ;
padding : 0.6 rem 0.9 rem ;
background : var ( -- color - background - mute ) ;
font - size : 0.88 rem ;
}
. cg - title {
font - weight : 600 ;
margin - right : auto ;
}
. cg - total {
font - weight : 700 ;
}
. rows {
list - style : none ;
margin : 0 ;
padding : 0 ;
}
. row3 {
display : grid ;
grid - template - columns : auto 1 fr auto ;
align - items : center ;
gap : 0.6 rem ;
padding : 0.65 rem 0.9 rem ;
border - top : 1 px solid var ( -- color - border ) ;
}
. row - rec {
display : flex ;
align - items : center ;
justify - content : space - between ;
gap : 0.6 rem ;
padding : 0.65 rem 0.9 rem ;
border - top : 1 px solid var ( -- color - border ) ;
}
. row - rec : first - child {
border - top : 0 ;
}
. rec - main {
display : flex ;
flex - direction : column ;
gap : 0.2 rem ;
min - width : 0 ;
}
. rec - title {
font - weight : 600 ;
font - size : 0.92 rem ;
}
. rec - sub {
font - size : 0.78 rem ;
opacity : 0.7 ;
}
. row - sub2 {
padding : 0.55 rem 0.9 rem ;
border - top : 1 px solid var ( -- color - border ) ;
font - size : 0.9 rem ;
}
. sub - mark {
opacity : 0.4 ;
margin - right : 0.4 rem ;
}
. major - badge {
font - size : 0.7 rem ;
padding : 0.1 rem 0.35 rem ;
border - radius : 4 px ;
background : hsla ( 160 , 100 % , 37 % , 0.15 ) ;
color : hsla ( 160 , 100 % , 37 % , 1 ) ;
font - weight : 700 ;
}
. tag {
font - size : 0.75 rem ;
padding : 0.1 rem 0.45 rem ;
border - radius : 999 px ;
background : var ( -- color - background - mute ) ;
white - space : nowrap ;
}
. ellip {
overflow : hidden ;
text - overflow : ellipsis ;
white - space : nowrap ;
font - size : 0.92 rem ;
}
. amt {
font - weight : 700 ;
white - space : nowrap ;
}
. locked - box {
text - align : center ;
padding : 2.5 rem 1 rem ;
border : 1 px dashed var ( -- color - border ) ;
border - radius : 12 px ;
}
. lock - big {
font - size : 2.2 rem ;
}
. lock - title {
margin - top : 0.5 rem ;
font - size : 1.05 rem ;
font - weight : 700 ;
}
. lock - desc {
margin : 0.4 rem 0 1.1 rem ;
font - size : 0.88 rem ;
opacity : 0.7 ;
}
. cta {
margin - top : 1.5 rem ;
text - align : center ;
}
. cta p {
font - size : 0.92 rem ;
opacity : 0.8 ;
margin - bottom : 0.75 rem ;
}
. cta . cta - btn {
width : 100 % ;
max - width : 320 px ;
padding : 0.8 rem 1 rem ;
font - size : 1 rem ;
}
@ media ( max - width : 768 px ) {
. demo - shell {
flex - direction : column ;
}
. demo - menu {
width : 100 % ;
flex - direction : row ;
overflow - x : auto ;
gap : 0.3 rem ;
}
. dm - item {
flex : none ;
white - space : nowrap ;
}
}
< / style >