238 lines
6.8 KiB
Vue
238 lines
6.8 KiB
Vue
<template>
|
||
<BasicModal
|
||
v-model:visible="visible"
|
||
width="400px"
|
||
destroy-on-close
|
||
custom-footer
|
||
wrap-class-name="app-premium-modal--scroll-tall"
|
||
>
|
||
<template #title>
|
||
<div class="app-premium-modal__title">
|
||
<BasicIcon icon="vuesax-linear:calculator" size="20" class="app-premium-modal__title-icon" />
|
||
<span>ماشین حساب</span>
|
||
</div>
|
||
</template>
|
||
|
||
<div class="gold-day-calc" dir="rtl">
|
||
<p class="gold-day-calc__hint text-xs text-gray-500">
|
||
وزن مورد نظر را وارد کنید تا ارزش آن بر اساس قیمت لحظهای محاسبه شود.
|
||
</p>
|
||
|
||
<BasicInput
|
||
v-model:value="weight"
|
||
:title="`وزن ${metalTypeLabel} (${weightUnit})`"
|
||
type="tel"
|
||
place-holder="مثلاً ۱۰.۵"
|
||
inputmode="decimal"
|
||
/>
|
||
|
||
<BasicInput
|
||
:value="unitPriceInput"
|
||
:title="`قیمت روز ${metalTypeLabel}`"
|
||
type="tel"
|
||
disabled
|
||
addon-before="ریال"
|
||
place-holder="قیمت صفحه اصلی"
|
||
/>
|
||
|
||
<BasicInput
|
||
v-model:value="feePercent"
|
||
title="اجرت ساخت مفتول (%)"
|
||
type="tel"
|
||
place-holder="مثلاً ۷"
|
||
inputmode="decimal"
|
||
/>
|
||
|
||
<BasicInput
|
||
v-model:value="profitPercent"
|
||
title="سود مورد انتظار (%)"
|
||
type="tel"
|
||
place-holder="مثلاً ۵"
|
||
inputmode="decimal"
|
||
/>
|
||
|
||
<BasicInput
|
||
v-model:value="taxPercent"
|
||
title="مالیات ارزش افزوده (%)"
|
||
type="tel"
|
||
place-holder="مثلاً ۱۰"
|
||
inputmode="decimal"
|
||
/>
|
||
|
||
<div class="gold-day-calc__result rounded-xl border border-solid border-gray-200 bg-gray-50 p-3">
|
||
<div class="text-xs text-gray-500 mb-1">کل نهایی</div>
|
||
<div class="gold-day-calc__total text-lg font-bold text-primary">
|
||
<span class="gold-day-calc__num">{{ formattedTotal }}</span>
|
||
<span class="text-sm font-medium text-gray-600">ریال</span>
|
||
</div>
|
||
<div class="mt-2 flex flex-col gap-1 text-xs text-gray-600">
|
||
<div class="flex justify-between gap-2">
|
||
<span>هزینه {{ metalTypeLabel }}</span>
|
||
<span class="gold-day-calc__num">{{ formatRial(breakdown.goldCost) }}</span>
|
||
</div>
|
||
<div class="flex justify-between gap-2">
|
||
<span>اجرت ساخت مفتول</span>
|
||
<span class="gold-day-calc__num">{{ formatRial(breakdown.feeCost) }}</span>
|
||
</div>
|
||
<div class="flex justify-between gap-2">
|
||
<span>سود مورد انتظار</span>
|
||
<span class="gold-day-calc__num">{{ formatRial(breakdown.profitCost) }}</span>
|
||
</div>
|
||
<div class="flex justify-between gap-2">
|
||
<span>مالیات ارزش افزوده</span>
|
||
<span class="gold-day-calc__num">{{ formatRial(breakdown.taxCost) }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<template #footer>
|
||
<BasicButton type="primary" class="app-premium-modal__btn-primary !w-full" size="middle" @click="recalculate">
|
||
محاسبه مجدد
|
||
</BasicButton>
|
||
</template>
|
||
</BasicModal>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, ref, watch } from 'vue'
|
||
import BasicModal from '@/components/BasicPacks/BasicModal.vue'
|
||
import BasicInput from '@/components/BasicPacks/BasicInput.vue'
|
||
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||
import { goldDayCalculatorVisible } from '@/composables/useGoldDayCalculator'
|
||
import {
|
||
getLastPrices,
|
||
getTradeBuyPrice,
|
||
lastPrice,
|
||
startLastPricePolling,
|
||
stopLastPricePolling,
|
||
} from '@/utils/price'
|
||
import { getPriceDisplayState, NO_QUOTE_MESSAGE } from '@/utils/storeHours'
|
||
import { usePriceWithoutType } from '@/utils/usePrice'
|
||
import { persianToEnglishDigits } from '@/utils/persianDigits'
|
||
import { useSettingsStore } from '@/store/settingsStore'
|
||
|
||
const visible = goldDayCalculatorVisible
|
||
const settingsStore = useSettingsStore()
|
||
|
||
const metalTypeLabel = computed(() => settingsStore.metalTypeLabel)
|
||
const weightUnit = computed(() => settingsStore.tradeWeightUnit)
|
||
|
||
const weight = ref<string | number>('')
|
||
const feePercent = ref<string | number>('')
|
||
const profitPercent = ref<string | number>('')
|
||
const taxPercent = ref<string | number>('')
|
||
const tick = ref(0)
|
||
|
||
function parseNum(raw: string | number | undefined): number {
|
||
const s = persianToEnglishDigits(String(raw ?? ''))
|
||
.replace(/,/g, '')
|
||
.replace(/[^\d.]/g, '')
|
||
const n = Number.parseFloat(s)
|
||
return Number.isFinite(n) ? n : 0
|
||
}
|
||
|
||
const liveUnitPrice = computed(() => {
|
||
void tick.value
|
||
if (getPriceDisplayState() !== 'open') return 0
|
||
return getTradeBuyPrice(lastPrice.value) || 0
|
||
})
|
||
|
||
const unitPriceInput = computed(() => {
|
||
void tick.value
|
||
const state = getPriceDisplayState()
|
||
if (state === 'closed') return 'فروشگاه بسته است'
|
||
if (state === 'no_quote') return NO_QUOTE_MESSAGE
|
||
const p = liveUnitPrice.value
|
||
return p > 0 ? usePriceWithoutType(p) : ''
|
||
})
|
||
|
||
const breakdown = computed(() => {
|
||
void tick.value
|
||
const w = parseNum(weight.value)
|
||
const fee = parseNum(feePercent.value)
|
||
const profit = parseNum(profitPercent.value)
|
||
const tax = parseNum(taxPercent.value)
|
||
const unit = liveUnitPrice.value
|
||
const goldCost = w * unit
|
||
const feeCost = goldCost * (fee / 100)
|
||
const afterFee = goldCost + feeCost
|
||
const profitCost = afterFee * (profit / 100)
|
||
const taxCost = (afterFee + profitCost) * (tax / 100)
|
||
const total = afterFee + profitCost + taxCost
|
||
return { goldCost, feeCost, profitCost, taxCost, total }
|
||
})
|
||
|
||
const formattedTotal = computed(() => formatRial(breakdown.value.total))
|
||
|
||
function formatRial(n: number) {
|
||
if (!Number.isFinite(n) || n <= 0) return '۰'
|
||
return usePriceWithoutType(Math.round(n))
|
||
}
|
||
|
||
function recalculate() {
|
||
tick.value += 1
|
||
void getLastPrices()
|
||
}
|
||
|
||
watch(visible, async (open) => {
|
||
if (open) {
|
||
startLastPricePolling()
|
||
await getLastPrices()
|
||
tick.value += 1
|
||
} else {
|
||
stopLastPricePolling()
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.gold-day-calc {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.75rem;
|
||
direction: rtl;
|
||
text-align: right;
|
||
}
|
||
|
||
.gold-day-calc :deep(.ant-input-group) {
|
||
direction: ltr;
|
||
display: table;
|
||
width: 100%;
|
||
}
|
||
|
||
.gold-day-calc :deep(.ant-input-group > .ant-input) {
|
||
direction: ltr;
|
||
text-align: left;
|
||
}
|
||
|
||
.gold-day-calc :deep(.ant-input:not(.ant-input-group *)) {
|
||
text-align: right;
|
||
}
|
||
|
||
.gold-day-calc__hint {
|
||
margin: 0;
|
||
line-height: 1.7;
|
||
}
|
||
|
||
.gold-day-calc__result {
|
||
direction: rtl;
|
||
text-align: right;
|
||
}
|
||
|
||
.gold-day-calc__total {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: baseline;
|
||
justify-content: flex-start;
|
||
gap: 0.35rem;
|
||
}
|
||
|
||
.gold-day-calc__num {
|
||
direction: ltr;
|
||
unicode-bidi: isolate;
|
||
display: inline-block;
|
||
}
|
||
</style>
|