stage 2: FIRE calculator, holdings tracker, settings store update
- calculator.vue: compound-growth formula to compute months/date to FIRE, Barista FIRE logic breakdown card, collapsible editable params (bound to settings store) - holdings.vue: manual portfolio entry with add/edit/delete, P&L display - holdings.ts: new pinia store with persist; totalValue/Cost/PnL computed - settings.ts: add annualReturn (6% default) - progress.vue: enhanced empty-state onboarding prompt Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,223 @@
|
||||
<script lang="ts" setup>
|
||||
import { useProgressStore } from '@/store/progress'
|
||||
import { useSettingsStore } from '@/store/settings'
|
||||
|
||||
defineOptions({ name: 'Calculator' })
|
||||
definePage({
|
||||
style: { navigationBarTitleText: 'FIRE 测算' },
|
||||
})
|
||||
|
||||
const progress = useProgressStore()
|
||||
const settings = useSettingsStore()
|
||||
|
||||
const currentAssets = computed(() =>
|
||||
progress.netAssets > 0 ? progress.netAssets : settings.initialCapital,
|
||||
)
|
||||
|
||||
const netAnnualExpense = computed(() => settings.annualExpense - settings.partTimeIncome)
|
||||
|
||||
const computedTarget = computed(() =>
|
||||
Math.round(netAnnualExpense.value / settings.withdrawRate),
|
||||
)
|
||||
|
||||
const monthlyRate = computed(() => (1 + settings.annualReturn) ** (1 / 12) - 1)
|
||||
|
||||
// n = log((FV·r + PMT) / (PV·r + PMT)) / log(1+r)
|
||||
const monthsToFire = computed(() => {
|
||||
const pv = currentAssets.value
|
||||
const fv = settings.target
|
||||
const pmt = settings.monthlyInvest
|
||||
const r = monthlyRate.value
|
||||
if (pv >= fv) return 0
|
||||
if (r <= 0) return pmt > 0 ? Math.ceil((fv - pv) / pmt) : Infinity
|
||||
const ratio = (fv * r + pmt) / (pv * r + pmt)
|
||||
if (ratio <= 0) return Infinity
|
||||
return Math.log(ratio) / Math.log(1 + r)
|
||||
})
|
||||
|
||||
const yearsLeft = computed(() => Math.floor(monthsToFire.value / 12))
|
||||
const monthsLeft = computed(() => Math.floor(monthsToFire.value % 12))
|
||||
|
||||
const fireDate = computed(() => {
|
||||
if (!isFinite(monthsToFire.value)) return '无法达成'
|
||||
const d = new Date()
|
||||
d.setMonth(d.getMonth() + Math.ceil(monthsToFire.value))
|
||||
return `${d.getFullYear()} 年 ${d.getMonth() + 1} 月`
|
||||
})
|
||||
|
||||
const showParams = ref(false)
|
||||
|
||||
const paramItems = computed(() => [
|
||||
{
|
||||
key: 'annualReturn',
|
||||
label: '年化收益率',
|
||||
unit: '%',
|
||||
getValue: () => (settings.annualReturn * 100).toFixed(1),
|
||||
setValue: (v: string) => { settings.annualReturn = Math.max(0, Number(v)) / 100 },
|
||||
},
|
||||
{
|
||||
key: 'monthlyInvest',
|
||||
label: '每月定投',
|
||||
unit: '元',
|
||||
getValue: () => String(settings.monthlyInvest),
|
||||
setValue: (v: string) => { settings.monthlyInvest = Math.max(0, Number(v)) },
|
||||
},
|
||||
{
|
||||
key: 'annualExpense',
|
||||
label: '年支出',
|
||||
unit: '元',
|
||||
getValue: () => String(settings.annualExpense),
|
||||
setValue: (v: string) => { settings.annualExpense = Math.max(0, Number(v)) },
|
||||
},
|
||||
{
|
||||
key: 'partTimeIncome',
|
||||
label: '兼职年收入',
|
||||
unit: '元',
|
||||
getValue: () => String(settings.partTimeIncome),
|
||||
setValue: (v: string) => { settings.partTimeIncome = Math.max(0, Number(v)) },
|
||||
},
|
||||
{
|
||||
key: 'withdrawRate',
|
||||
label: '安全提取率',
|
||||
unit: '%',
|
||||
getValue: () => (settings.withdrawRate * 100).toFixed(1),
|
||||
setValue: (v: string) => { settings.withdrawRate = Math.max(0.1, Number(v)) / 100 },
|
||||
},
|
||||
{
|
||||
key: 'target',
|
||||
label: '目标本金',
|
||||
unit: '元',
|
||||
getValue: () => String(settings.target),
|
||||
setValue: (v: string) => { settings.target = Math.max(0, Number(v)) },
|
||||
},
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="min-h-screen bg-gray-50 px-4 pt-12 pb-24 flex flex-col items-center justify-center">
|
||||
<view class="text-6xl mb-4">🚧</view>
|
||||
<view class="text-lg font-medium text-gray-600">测算器</view>
|
||||
<view class="mt-2 text-sm text-gray-400">阶段 2 开发中</view>
|
||||
<view class="min-h-screen bg-gray-50 px-4 pt-12 pb-24">
|
||||
<view class="mb-6 text-2xl font-bold text-gray-800">
|
||||
FIRE 测算
|
||||
</view>
|
||||
|
||||
<!-- 核心结论 -->
|
||||
<view class="mb-4 rounded-2xl bg-green-500 p-5 shadow-sm text-white">
|
||||
<view class="text-sm opacity-80 mb-1">
|
||||
按当前定投速度,预计再过
|
||||
</view>
|
||||
<view class="text-3xl font-bold mb-1">
|
||||
<template v-if="monthsToFire === 0">
|
||||
已达成!🎉
|
||||
</template>
|
||||
<template v-else-if="isFinite(monthsToFire)">
|
||||
{{ yearsLeft }} 年 {{ monthsLeft }} 个月
|
||||
</template>
|
||||
<template v-else>
|
||||
请调整参数
|
||||
</template>
|
||||
</view>
|
||||
<view class="text-sm opacity-80">
|
||||
达到 Barista FIRE
|
||||
</view>
|
||||
<view v-if="isFinite(monthsToFire) && monthsToFire > 0" class="mt-3 text-base font-medium">
|
||||
预计时间:{{ fireDate }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 关键数字 -->
|
||||
<view class="mb-4 grid grid-cols-2 gap-3">
|
||||
<view class="rounded-2xl bg-white p-4 shadow-sm">
|
||||
<view class="text-sm text-gray-500">
|
||||
FIRE 目标本金
|
||||
</view>
|
||||
<view class="mt-1 text-xl font-bold text-gray-800">
|
||||
¥{{ (settings.target / 10000).toFixed(0) }} 万
|
||||
</view>
|
||||
<view class="mt-1 text-xs text-gray-400">
|
||||
按参数计算 {{ (computedTarget / 10000).toFixed(1) }} 万
|
||||
</view>
|
||||
</view>
|
||||
<view class="rounded-2xl bg-white p-4 shadow-sm">
|
||||
<view class="text-sm text-gray-500">
|
||||
当前净资产
|
||||
</view>
|
||||
<view class="mt-1 text-xl font-bold text-gray-800">
|
||||
¥{{ (currentAssets / 10000).toFixed(2) }} 万
|
||||
</view>
|
||||
<view class="mt-1 text-xs text-gray-400">
|
||||
{{ progress.netAssets > 0 ? '进度页最新记录' : '使用初始资本' }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="rounded-2xl bg-white p-4 shadow-sm">
|
||||
<view class="text-sm text-gray-500">
|
||||
每月定投
|
||||
</view>
|
||||
<view class="mt-1 text-xl font-bold text-blue-500">
|
||||
¥{{ settings.monthlyInvest.toLocaleString() }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="rounded-2xl bg-white p-4 shadow-sm">
|
||||
<view class="text-sm text-gray-500">
|
||||
年化收益假设
|
||||
</view>
|
||||
<view class="mt-1 text-xl font-bold text-purple-500">
|
||||
{{ (settings.annualReturn * 100).toFixed(1) }}%
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Barista FIRE 逻辑拆解 -->
|
||||
<view class="mb-4 rounded-2xl bg-white p-5 shadow-sm">
|
||||
<view class="font-medium text-gray-700 mb-3">
|
||||
Barista FIRE 逻辑
|
||||
</view>
|
||||
<view class="text-sm text-gray-600 space-y-2">
|
||||
<view class="flex justify-between">
|
||||
<text>年支出</text>
|
||||
<text class="font-medium text-gray-800">¥{{ settings.annualExpense.toLocaleString() }}</text>
|
||||
</view>
|
||||
<view class="flex justify-between">
|
||||
<text>兼职年收入</text>
|
||||
<text class="font-medium text-green-600">− ¥{{ settings.partTimeIncome.toLocaleString() }}</text>
|
||||
</view>
|
||||
<view class="h-px bg-gray-100 my-1" />
|
||||
<view class="flex justify-between">
|
||||
<text>投资需覆盖</text>
|
||||
<text class="font-medium text-gray-800">¥{{ netAnnualExpense.toLocaleString() }}/年</text>
|
||||
</view>
|
||||
<view class="flex justify-between">
|
||||
<text>÷ 提取率 {{ (settings.withdrawRate * 100).toFixed(1) }}%</text>
|
||||
<text class="font-medium text-green-600 text-right">= ¥{{ (computedTarget / 10000).toFixed(1) }} 万</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 调整参数(可展开) -->
|
||||
<view class="mb-4 rounded-2xl bg-white shadow-sm overflow-hidden">
|
||||
<view
|
||||
class="px-5 py-3 border-b border-gray-100 font-medium text-gray-700 flex justify-between items-center"
|
||||
@tap="showParams = !showParams"
|
||||
>
|
||||
<text>调整参数</text>
|
||||
<text class="text-gray-400 text-sm">{{ showParams ? '收起 ▲' : '展开 ▼' }}</text>
|
||||
</view>
|
||||
<view v-if="showParams" class="divide-y divide-gray-50">
|
||||
<view
|
||||
v-for="item in paramItems"
|
||||
:key="item.key"
|
||||
class="flex items-center justify-between px-5 py-3"
|
||||
>
|
||||
<text class="text-sm text-gray-700">{{ item.label }}({{ item.unit }})</text>
|
||||
<input
|
||||
:value="item.getValue()"
|
||||
type="digit"
|
||||
class="w-28 text-right border border-gray-200 rounded-lg px-3 py-1.5 text-sm text-gray-800"
|
||||
@blur="(e: any) => item.setValue(e.detail.value)"
|
||||
/>
|
||||
</view>
|
||||
<view class="px-5 py-3 text-xs text-gray-400">
|
||||
修改后自动保存,数据实时更新
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -1,14 +1,221 @@
|
||||
<script lang="ts" setup>
|
||||
import { useHoldingsStore } from '@/store/holdings'
|
||||
|
||||
defineOptions({ name: 'Holdings' })
|
||||
definePage({
|
||||
style: { navigationBarTitleText: '持仓' },
|
||||
})
|
||||
|
||||
const store = useHoldingsStore()
|
||||
|
||||
const showForm = ref(false)
|
||||
const editingId = ref<string | null>(null)
|
||||
const form = reactive({ name: '', currentValue: '', costBasis: '' })
|
||||
|
||||
function openAdd() {
|
||||
editingId.value = null
|
||||
form.name = ''
|
||||
form.currentValue = ''
|
||||
form.costBasis = ''
|
||||
showForm.value = true
|
||||
}
|
||||
|
||||
function openEdit(id: string) {
|
||||
const h = store.holdings.find(h => h.id === id)
|
||||
if (!h) return
|
||||
editingId.value = id
|
||||
form.name = h.name
|
||||
form.currentValue = String(h.currentValue)
|
||||
form.costBasis = String(h.costBasis)
|
||||
showForm.value = true
|
||||
}
|
||||
|
||||
function saveForm() {
|
||||
const cv = Number(form.currentValue)
|
||||
const cb = Number(form.costBasis)
|
||||
if (!form.name.trim() || cv <= 0 || cb <= 0) return
|
||||
if (editingId.value) {
|
||||
store.update(editingId.value, cv, cb)
|
||||
}
|
||||
else {
|
||||
store.add(form.name.trim(), cv, cb)
|
||||
}
|
||||
showForm.value = false
|
||||
}
|
||||
|
||||
function confirmDelete(id: string) {
|
||||
uni.showModal({
|
||||
title: '删除持仓',
|
||||
content: '确认删除这条记录?',
|
||||
success: (res) => {
|
||||
if (res.confirm) store.remove(id)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function pnlColor(pnl: number) {
|
||||
return pnl >= 0 ? 'text-red-500' : 'text-green-600'
|
||||
}
|
||||
|
||||
function pnlSign(pnl: number) {
|
||||
return pnl >= 0 ? '+' : ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="min-h-screen bg-gray-50 px-4 pt-12 pb-24 flex flex-col items-center justify-center">
|
||||
<view class="text-6xl mb-4">🚧</view>
|
||||
<view class="text-lg font-medium text-gray-600">持仓跟踪</view>
|
||||
<view class="mt-2 text-sm text-gray-400">阶段 2 开发中</view>
|
||||
<view class="min-h-screen bg-gray-50 px-4 pt-12 pb-24">
|
||||
<view class="mb-6 text-2xl font-bold text-gray-800">
|
||||
持仓
|
||||
</view>
|
||||
|
||||
<!-- 总览卡片 -->
|
||||
<view class="mb-4 rounded-2xl bg-white p-5 shadow-sm">
|
||||
<view class="mb-3 text-sm text-gray-500">
|
||||
投资组合总览
|
||||
</view>
|
||||
<view class="flex justify-between items-end mb-4">
|
||||
<view>
|
||||
<view class="text-xs text-gray-400 mb-1">
|
||||
总市值
|
||||
</view>
|
||||
<view class="text-2xl font-bold text-gray-800">
|
||||
¥{{ store.totalValue.toLocaleString() }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="text-right">
|
||||
<view class="text-xs text-gray-400 mb-1">
|
||||
总浮盈
|
||||
</view>
|
||||
<view class="text-xl font-bold" :class="pnlColor(store.totalPnl)">
|
||||
{{ pnlSign(store.totalPnl) }}¥{{ Math.abs(store.totalPnl).toLocaleString() }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex justify-between text-sm text-gray-500">
|
||||
<view>
|
||||
总成本 ¥{{ store.totalCost.toLocaleString() }}
|
||||
</view>
|
||||
<view :class="pnlColor(store.totalPnl)">
|
||||
{{ pnlSign(store.totalPnlPct) }}{{ store.totalPnlPct.toFixed(2) }}%
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 持仓列表 -->
|
||||
<view class="mb-4 rounded-2xl bg-white shadow-sm overflow-hidden">
|
||||
<view class="px-5 py-3 border-b border-gray-100 font-medium text-gray-700">
|
||||
持仓明细
|
||||
</view>
|
||||
|
||||
<view v-if="store.holdings.length === 0" class="px-5 py-10 flex flex-col items-center gap-3">
|
||||
<view class="text-4xl">
|
||||
📈
|
||||
</view>
|
||||
<view class="text-base font-medium text-gray-700">
|
||||
暂无持仓记录
|
||||
</view>
|
||||
<view class="text-sm text-gray-400 text-center">
|
||||
点击右下角 <text class="font-bold text-green-500">+</text> 按钮添加持仓
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-for="h in store.holdings"
|
||||
:key="h.id"
|
||||
class="px-5 py-4 border-b border-gray-50 last:border-none"
|
||||
>
|
||||
<view class="flex justify-between items-start mb-1">
|
||||
<text class="font-medium text-gray-800 text-base">{{ h.name }}</text>
|
||||
<view class="text-right">
|
||||
<view class="font-bold text-gray-800">
|
||||
¥{{ h.currentValue.toLocaleString() }}
|
||||
</view>
|
||||
<view class="text-sm" :class="pnlColor(h.currentValue - h.costBasis)">
|
||||
{{ pnlSign(h.currentValue - h.costBasis) }}{{ ((h.currentValue - h.costBasis) / h.costBasis * 100).toFixed(2) }}%
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex justify-between items-center mt-1">
|
||||
<text class="text-xs text-gray-400">成本 ¥{{ h.costBasis.toLocaleString() }}</text>
|
||||
<view class="flex gap-3">
|
||||
<text class="text-xs text-blue-400" @tap="openEdit(h.id)">编辑</text>
|
||||
<text class="text-xs text-red-400" @tap="confirmDelete(h.id)">删除</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- FAB -->
|
||||
<view
|
||||
class="fixed bottom-20 right-5 h-14 w-14 rounded-full bg-green-500 shadow-lg flex items-center justify-center"
|
||||
@tap="openAdd"
|
||||
>
|
||||
<text class="text-white text-2xl leading-none">+</text>
|
||||
</view>
|
||||
|
||||
<!-- 录入/编辑弹窗 -->
|
||||
<view
|
||||
v-if="showForm"
|
||||
class="fixed inset-0 bg-black/40 flex items-center px-6"
|
||||
@tap.self="showForm = false"
|
||||
>
|
||||
<view class="w-full rounded-2xl bg-white p-6">
|
||||
<view class="mb-4 text-lg font-medium text-gray-800">
|
||||
{{ editingId ? '编辑持仓' : '添加持仓' }}
|
||||
</view>
|
||||
<view class="space-y-3">
|
||||
<view>
|
||||
<view class="text-sm text-gray-500 mb-1">
|
||||
名称(如:沪深300ETF)
|
||||
</view>
|
||||
<input
|
||||
v-model="form.name"
|
||||
type="text"
|
||||
placeholder="请输入持仓名称"
|
||||
class="w-full rounded-xl border border-gray-200 px-4 py-3 text-base"
|
||||
:adjust-position="false"
|
||||
/>
|
||||
</view>
|
||||
<view>
|
||||
<view class="text-sm text-gray-500 mb-1">
|
||||
当前市值(元)
|
||||
</view>
|
||||
<input
|
||||
v-model="form.currentValue"
|
||||
type="digit"
|
||||
placeholder="当前总市值"
|
||||
class="w-full rounded-xl border border-gray-200 px-4 py-3 text-base"
|
||||
:adjust-position="false"
|
||||
/>
|
||||
</view>
|
||||
<view>
|
||||
<view class="text-sm text-gray-500 mb-1">
|
||||
成本(元)
|
||||
</view>
|
||||
<input
|
||||
v-model="form.costBasis"
|
||||
type="digit"
|
||||
placeholder="买入总成本"
|
||||
class="w-full rounded-xl border border-gray-200 px-4 py-3 text-base"
|
||||
:adjust-position="false"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="mt-5 flex gap-3">
|
||||
<view
|
||||
class="flex-1 rounded-xl border border-gray-200 py-3 text-center text-gray-500"
|
||||
@tap="showForm = false"
|
||||
>
|
||||
取消
|
||||
</view>
|
||||
<view
|
||||
class="flex-1 rounded-xl bg-green-500 py-3 text-center text-white font-medium"
|
||||
@tap="saveForm"
|
||||
>
|
||||
保存
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -83,8 +83,19 @@ function saveSnapshot() {
|
||||
<view class="px-5 py-3 border-b border-gray-100 font-medium text-gray-700">
|
||||
历史记录
|
||||
</view>
|
||||
<view v-if="store.snapshots.length === 0" class="px-5 py-8 text-center text-gray-400">
|
||||
暂无记录,点击下方按钮录入
|
||||
<view v-if="store.snapshots.length === 0" class="px-5 py-10 flex flex-col items-center gap-3">
|
||||
<view class="text-4xl">
|
||||
📊
|
||||
</view>
|
||||
<view class="text-base font-medium text-gray-700">
|
||||
记录你的第一笔净资产
|
||||
</view>
|
||||
<view class="text-sm text-gray-400 text-center leading-relaxed">
|
||||
净资产 = 所有存款 + 投资账户 − 负债<br>点击右下角 <text class="font-bold text-green-500">+</text> 按钮录入当前总资产,开始追踪 FIRE 进度。
|
||||
</view>
|
||||
<view class="mt-1 text-xs text-gray-300">
|
||||
建议每月或每季度更新一次
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-for="s in [...store.snapshots].reverse().slice(0, 10)"
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export interface Holding {
|
||||
id: string
|
||||
name: string
|
||||
currentValue: number
|
||||
costBasis: number
|
||||
}
|
||||
|
||||
export const useHoldingsStore = defineStore('holdings', () => {
|
||||
const holdings = ref<Holding[]>([])
|
||||
|
||||
const totalValue = computed(() => holdings.value.reduce((s, h) => s + h.currentValue, 0))
|
||||
const totalCost = computed(() => holdings.value.reduce((s, h) => s + h.costBasis, 0))
|
||||
const totalPnl = computed(() => totalValue.value - totalCost.value)
|
||||
const totalPnlPct = computed(() =>
|
||||
totalCost.value > 0 ? (totalPnl.value / totalCost.value) * 100 : 0,
|
||||
)
|
||||
|
||||
function add(name: string, currentValue: number, costBasis: number) {
|
||||
holdings.value.push({
|
||||
id: Date.now().toString(),
|
||||
name,
|
||||
currentValue,
|
||||
costBasis,
|
||||
})
|
||||
}
|
||||
|
||||
function update(id: string, currentValue: number, costBasis: number) {
|
||||
const h = holdings.value.find(h => h.id === id)
|
||||
if (h) {
|
||||
h.currentValue = currentValue
|
||||
h.costBasis = costBasis
|
||||
}
|
||||
}
|
||||
|
||||
function remove(id: string) {
|
||||
holdings.value = holdings.value.filter(h => h.id !== id)
|
||||
}
|
||||
|
||||
return { holdings, totalValue, totalCost, totalPnl, totalPnlPct, add, update, remove }
|
||||
}, { persist: true })
|
||||
@@ -1,12 +1,13 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useSettingsStore = defineStore('settings', () => {
|
||||
const target = ref(710000) // 目標本金(元)
|
||||
const target = ref(710000) // 目标本金(元)
|
||||
const withdrawRate = ref(0.035) // 安全提取率 3.5%
|
||||
const monthlyInvest = ref(1500) // 每月定投(元)
|
||||
const partTimeIncome = ref(40000) // 兼職年收入(元)
|
||||
const annualExpense = ref(65000) // 年開支(元)
|
||||
const initialCapital = ref(70000) // 起始本金(元)
|
||||
const partTimeIncome = ref(40000) // 兼职年收入(元)
|
||||
const annualExpense = ref(65000) // 年支出(元)
|
||||
const initialCapital = ref(70000) // 起始本金(元),无进度记录时的回退值
|
||||
const annualReturn = ref(0.06) // 投资年化收益率 6%
|
||||
|
||||
return {
|
||||
target,
|
||||
@@ -15,6 +16,7 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||
partTimeIncome,
|
||||
annualExpense,
|
||||
initialCapital,
|
||||
annualReturn,
|
||||
}
|
||||
}, {
|
||||
persist: true,
|
||||
|
||||
Reference in New Issue
Block a user