feat: 홈 일별 캘린더·시세 갱신·게시판 카테고리·약관동의·계좌 마스킹
- 홈: 6월 예산 아래 일별 수입/지출 캘린더(마우스오버/탭 시 내역 목록) - 투자: 시세 자동조회 버튼/진입 시 갱신, 보유종목 2줄 표기, 평가액 직접입력 - 게시판: 커뮤니티/짠테크 수다방/재테크 팁 분리, 사이드바 영역 구분선 - 회원가입: 이용약관·개인정보 수집 동의(필수 체크) - 보안: 계좌번호 화면 마스킹(끝 4자리+눈 토글) - 정기→고정지출, 내역/정기 라디오·sticky 저장, 태그 드래그 정렬 - fix: 모바일웹 새로고침 시 로그인 팝업(restore localStorage 우선) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -70,6 +70,27 @@ async function load() {
|
||||
}
|
||||
}
|
||||
|
||||
// 종목코드로 현재가 시세 일괄 갱신
|
||||
const refreshing = ref(false)
|
||||
const refreshMsg = ref('')
|
||||
async function refreshPrices() {
|
||||
if (refreshing.value) return
|
||||
refreshing.value = true
|
||||
refreshMsg.value = ''
|
||||
try {
|
||||
holdings.value = await accountApi.refreshPrices(props.walletId)
|
||||
emit('changed')
|
||||
const noTicker = holdings.value.filter((h) => !h.ticker).length
|
||||
refreshMsg.value = noTicker
|
||||
? `시세 갱신 완료 · 종목코드 없는 ${noTicker}개는 제외`
|
||||
: '시세 갱신 완료'
|
||||
} catch {
|
||||
refreshMsg.value = '시세 갱신에 실패했습니다.'
|
||||
} finally {
|
||||
refreshing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 종목 ===== */
|
||||
function openAddHolding() {
|
||||
holdingEditId.value = null
|
||||
@@ -189,15 +210,29 @@ async function removeTrade(t, holdingId) {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
onMounted(async () => {
|
||||
await load()
|
||||
// 투자 계좌를 펼치면 종목코드 있는 종목 현재가를 자동으로 시세 갱신
|
||||
if (holdings.value.some((h) => h.ticker)) {
|
||||
refreshPrices()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="portfolio">
|
||||
<div class="pf-head">
|
||||
<span class="pf-title">보유 종목</span>
|
||||
<IconBtn icon="plus" title="종목 추가" variant="primary" size="sm" @click="openAddHolding" />
|
||||
<div class="pf-head-btns">
|
||||
<button
|
||||
type="button" class="refresh-btn"
|
||||
:disabled="refreshing || !holdings.length"
|
||||
@click="refreshPrices"
|
||||
>{{ refreshing ? '갱신 중…' : '↻ 시세 갱신' }}</button>
|
||||
<IconBtn icon="plus" title="종목 추가" variant="primary" size="sm" @click="openAddHolding" />
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="refreshMsg" class="pf-refresh-msg">{{ refreshMsg }}</p>
|
||||
|
||||
<p v-if="loading" class="pf-msg">불러오는 중...</p>
|
||||
<p v-else-if="!holdings.length" class="pf-msg">보유 종목이 없습니다. 종목을 추가하고 매수를 기록하세요.</p>
|
||||
@@ -210,9 +245,12 @@ onMounted(load)
|
||||
<div>
|
||||
<div class="h-name">{{ h.name }}<span v-if="h.ticker" class="h-ticker">{{ h.ticker }}</span></div>
|
||||
<div class="h-sub">
|
||||
{{ shares(h.quantity) }}주 · 평단 {{ won(h.avgPrice) }}
|
||||
<template v-if="h.currentPrice != null"> · 현재가 {{ won(h.currentPrice) }}</template>
|
||||
<span v-else class="noprice"> · 현재가 미입력</span>
|
||||
<div class="h-sub-qty">{{ shares(h.quantity) }}주</div>
|
||||
<div class="h-sub-price">
|
||||
평단 {{ won(h.avgPrice) }}
|
||||
<template v-if="h.currentPrice != null"> · 현재가 {{ won(h.currentPrice) }}</template>
|
||||
<span v-else class="noprice"> · 현재가 미입력</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -257,8 +295,9 @@ onMounted(load)
|
||||
<h2>종목 {{ holdingEditId ? '수정' : '추가' }}</h2>
|
||||
<form class="pf-form" @submit.prevent="submitHolding">
|
||||
<label>종목명<input v-model="hForm.name" type="text" placeholder="예: 삼성전자" :disabled="hSubmitting" /></label>
|
||||
<label>종목코드<input v-model="hForm.ticker" type="text" placeholder="(선택) 예: 005930" :disabled="hSubmitting" /></label>
|
||||
<label>현재가<input v-model.number="hForm.currentPrice" type="number" min="0" placeholder="(수동 갱신)" :disabled="hSubmitting" /></label>
|
||||
<label>종목코드<input v-model="hForm.ticker" type="text" placeholder="예: 005930 (국내 상장)" :disabled="hSubmitting" /></label>
|
||||
<label>현재가<input v-model.number="hForm.currentPrice" type="number" min="0" placeholder="(비우면 시세 갱신으로 자동)" :disabled="hSubmitting" /></label>
|
||||
<p class="pf-form-hint">종목코드(6자리)를 입력하면 <b>시세 갱신</b> 버튼으로 현재가를 자동으로 불러옵니다.</p>
|
||||
<p v-if="hError" class="pf-msg err">{{ hError }}</p>
|
||||
<div class="pf-buttons">
|
||||
<IconBtn icon="close" title="취소" @click="holdingModal = false" />
|
||||
@@ -330,6 +369,35 @@ onMounted(load)
|
||||
font-weight: 600;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.pf-head-btns {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
.refresh-btn {
|
||||
padding: 0.25rem 0.6rem;
|
||||
font-size: 0.78rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
background: var(--color-background-soft);
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.refresh-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
.pf-refresh-msg {
|
||||
font-size: 0.78rem;
|
||||
opacity: 0.7;
|
||||
margin: 0 0 0.4rem;
|
||||
}
|
||||
.pf-form-hint {
|
||||
font-size: 0.76rem;
|
||||
opacity: 0.6;
|
||||
margin: -0.2rem 0 0;
|
||||
}
|
||||
.pf-add {
|
||||
padding: 0.25rem 0.6rem;
|
||||
font-size: 0.8rem;
|
||||
@@ -389,6 +457,12 @@ onMounted(load)
|
||||
font-size: 0.76rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.h-sub-qty {
|
||||
font-weight: 600;
|
||||
}
|
||||
.h-sub-price {
|
||||
margin-top: 1px;
|
||||
}
|
||||
.noprice {
|
||||
color: #e67e22;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user