feat: 홈 일별 캘린더·시세 갱신·게시판 카테고리·약관동의·계좌 마스킹

- 홈: 6월 예산 아래 일별 수입/지출 캘린더(마우스오버/탭 시 내역 목록)
- 투자: 시세 자동조회 버튼/진입 시 갱신, 보유종목 2줄 표기, 평가액 직접입력
- 게시판: 커뮤니티/짠테크 수다방/재테크 팁 분리, 사이드바 영역 구분선
- 회원가입: 이용약관·개인정보 수집 동의(필수 체크)
- 보안: 계좌번호 화면 마스킹(끝 4자리+눈 토글)
- 정기→고정지출, 내역/정기 라디오·sticky 저장, 태그 드래그 정렬
- fix: 모바일웹 새로고침 시 로그인 팝업(restore localStorage 우선)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ByungCheol
2026-06-04 05:01:33 +09:00
parent 67aa635dd8
commit 4e6a89fd7a
17 changed files with 1022 additions and 136 deletions
+13 -21
View File
@@ -4,6 +4,7 @@ import { useRoute, useRouter } from 'vue-router'
import { boardApi } from '@/api/boardApi'
import { formatRelative } from '@/utils/datetime'
import { useAuthStore } from '@/stores/auth'
import { boardLabel, DEFAULT_BOARD } from '@/constants/boards'
import IconBtn from '@/components/ui/IconBtn.vue'
const route = useRoute()
@@ -11,6 +12,10 @@ const router = useRouter()
const auth = useAuthStore()
const isAdmin = computed(() => auth.user?.role === 'ADMIN')
// 현재 게시판(카테고리)
const category = computed(() => route.params.category || DEFAULT_BOARD)
const boardTitle = computed(() => boardLabel(category.value))
const posts = ref([])
const tags = ref([])
const loading = ref(false)
@@ -38,6 +43,7 @@ async function load() {
error.value = null
try {
const res = await boardApi.list({
category: category.value,
page: page.value,
size: size.value,
tag: activeTag.value,
@@ -107,14 +113,9 @@ function clearSearch() {
pushQuery({ page: 1, tag: activeTag.value })
}
// 목록 표시 순번: 전체건수 기준 내림차순 (최신글이 큰 번호)
function rowNumber(index) {
return totalElements.value - (page.value - 1) * size.value - index
}
// 쿼리가 바뀔 때마다(검색·페이지 이동·뒤로가기 포함) 상태 동기화 후 재조회
// 경로(게시판 전환)·쿼리(검색·페이지·뒤로가기)가 바뀌면 상태 동기화 후 재조회
watch(
() => route.query,
() => route.fullPath,
() => {
syncFromQuery()
load()
@@ -128,8 +129,8 @@ onMounted(loadTags)
<template>
<section class="board">
<header class="board-head">
<h1>자유게시판</h1>
<IconBtn icon="edit" title="글쓰기" variant="primary" @click="router.push('/board/write')" />
<h1>{{ boardTitle }}</h1>
<IconBtn icon="edit" title="글쓰기" variant="primary" @click="router.push(`/board/${category}/write`)" />
</header>
<div v-if="tags.length" class="tag-filter">
@@ -155,7 +156,6 @@ onMounted(loadTags)
<table v-else-if="posts.length" class="post-table">
<thead>
<tr>
<th class="col-id">번호</th>
<th>제목</th>
<th class="col-author">작성자</th>
<th class="col-num">조회</th>
@@ -163,8 +163,7 @@ onMounted(loadTags)
</tr>
</thead>
<tbody>
<tr v-for="(p, index) in posts" :key="p.id" @click="router.push(`/board/${p.id}`)">
<td class="col-id">{{ rowNumber(index) }}</td>
<tr v-for="p in posts" :key="p.id" @click="router.push(`/board/${category}/${p.id}`)">
<td>
<template v-if="p.blocked && !isAdmin">
<span class="blocked-msg">🚫 관리자에 의해 제한된 게시글입니다.</span>
@@ -291,7 +290,6 @@ button:disabled {
.post-table tbody tr:hover {
background: var(--color-background-soft);
}
.col-id,
.col-num {
width: 60px;
text-align: center;
@@ -440,20 +438,14 @@ button:disabled {
font-size: 0.8rem;
opacity: 0.7;
}
/* 제목(2번째 셀): 첫 줄 전체 */
.post-table td:nth-child(2) {
/* 제목(번째 셀): 첫 줄 전체 */
.post-table td:nth-child(1) {
order: 0;
width: 100% !important;
font-size: 0.97rem;
opacity: 1;
font-weight: 500;
}
.post-table .col-id {
order: 1;
}
.post-table .col-id::before {
content: '#';
}
.post-table .col-author {
order: 2;
}