- 환율 자동조회 후 '방금 업데이트 / N분 전 / N시간 전' 표시 - 30초 간격 ticker로 레이블 자동 갱신 - 폼 열기/통화 변경 시 rateUpdatedAt 초기화 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { computed, nextTick, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
|
||||
import { accountApi } from '@/api/accountApi'
|
||||
import { reminders } from '@/native/reminders'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
@@ -99,14 +99,31 @@ watch([() => form.currency, () => form.foreignAmount, () => form.rate], () => {
|
||||
if (isForeign.value) form.amount = convertedKrw.value
|
||||
})
|
||||
const fxLoading = ref(false)
|
||||
const rateUpdatedAt = ref(null) // 환율 마지막 조회 시각(ms)
|
||||
const tickNow = ref(Date.now())
|
||||
let _fxTick = null
|
||||
onMounted(() => { _fxTick = setInterval(() => { tickNow.value = Date.now() }, 30_000) })
|
||||
onUnmounted(() => clearInterval(_fxTick))
|
||||
const rateAgeLabel = computed(() => {
|
||||
if (!rateUpdatedAt.value || !isForeign.value) return null
|
||||
const diff = Math.floor((tickNow.value - rateUpdatedAt.value) / 1000)
|
||||
if (diff < 60) return '방금 업데이트'
|
||||
if (diff < 3600) return `${Math.floor(diff / 60)}분 전`
|
||||
if (diff < 86400) return `${Math.floor(diff / 3600)}시간 전`
|
||||
return `${Math.floor(diff / 86400)}일 전`
|
||||
})
|
||||
async function fetchRate() {
|
||||
if (!isForeign.value || fxLoading.value) return
|
||||
fxLoading.value = true
|
||||
try {
|
||||
const res = await accountApi.fxRate(form.currency, 'KRW')
|
||||
// 환율은 소수점 4자리로 통일(입력폼 step·저장 정밀도 일치)
|
||||
if (res?.rate != null) form.rate = Math.round(Number(res.rate) * 10000) / 10000
|
||||
else formInfo.value = '환율을 가져오지 못했어요. 직접 입력해 주세요.'
|
||||
if (res?.rate != null) {
|
||||
form.rate = Math.round(Number(res.rate) * 10000) / 10000
|
||||
rateUpdatedAt.value = res.updatedAt ? new Date(res.updatedAt).getTime() : Date.now()
|
||||
} else {
|
||||
formInfo.value = '환율을 가져오지 못했어요. 직접 입력해 주세요.'
|
||||
}
|
||||
} catch {
|
||||
formInfo.value = '환율 조회에 실패했어요. 직접 입력해 주세요.'
|
||||
} finally {
|
||||
@@ -120,6 +137,7 @@ function onCurrencyChange() {
|
||||
} else {
|
||||
form.foreignAmount = null
|
||||
form.rate = null
|
||||
rateUpdatedAt.value = null
|
||||
}
|
||||
}
|
||||
// 계좌/카드 (repayTargetIsCard·repayTargetLoanWallet computed가 참조하므로 먼저 선언)
|
||||
@@ -500,6 +518,7 @@ function openCreate() {
|
||||
editId.value = null
|
||||
editingPending.value = false
|
||||
Object.assign(form, { entryDate: todayStr(), type: 'EXPENSE', category: '', amount: null, memo: '', walletKind: 'BANK', walletId: '', toWalletKind: '', toWalletId: '', principal: null, interest: null, annualFee: null, installmentMonths: '', currency: 'KRW', foreignAmount: null, rate: null })
|
||||
rateUpdatedAt.value = null
|
||||
selectedTagIds.value = []
|
||||
formError.value = null
|
||||
formInfo.value = ''
|
||||
@@ -540,6 +559,7 @@ function openEdit(e) {
|
||||
const nameToId = {}
|
||||
tagOptions.value.forEach((t) => (nameToId[t.name] = t.id))
|
||||
selectedTagIds.value = (e.tags || []).map((n) => nameToId[n]).filter(Boolean)
|
||||
rateUpdatedAt.value = null
|
||||
pasteDetected.value = null
|
||||
pasteModalOpen.value = false
|
||||
pasteText.value = ''
|
||||
@@ -1212,6 +1232,7 @@ onMounted(async () => {
|
||||
<input v-model.number="form.rate" type="number" min="0" step="0.0001" placeholder="원" :disabled="submitting" />
|
||||
<button type="button" class="fx-auto" :disabled="submitting || fxLoading" @click="fetchRate">{{ fxLoading ? '…' : '↻ 자동' }}</button>
|
||||
</div>
|
||||
<span v-if="rateAgeLabel" class="fx-age">{{ rateAgeLabel }}</span>
|
||||
</div>
|
||||
<span class="fx-krw">= ₩{{ (convertedKrw || 0).toLocaleString('ko-KR') }}</span>
|
||||
</div>
|
||||
@@ -2130,6 +2151,11 @@ button.primary {
|
||||
font-size: 0.9rem;
|
||||
padding-bottom: 0.45rem;
|
||||
}
|
||||
.fx-age {
|
||||
font-size: 0.7rem;
|
||||
color: var(--color-text-soft, #999);
|
||||
margin-top: 0.18rem;
|
||||
}
|
||||
.row-wallet {
|
||||
margin-right: 0.35rem;
|
||||
font-size: 0.75rem;
|
||||
|
||||
Reference in New Issue
Block a user