304 lines
7.4 KiB
Vue
304 lines
7.4 KiB
Vue
<template>
|
||||
|
|
<BasicModal
|
|||
|
|
v-model:visible="visible"
|
|||
|
|
width="400px"
|
|||
|
|
destroy-on-close
|
|||
|
|
custom-footer
|
|||
|
|
wrap-class-name="gold-day-calc-modal"
|
|||
|
|
>
|
|||
|
|
<template #title>
|
|||
|
|
<div class="gold-day-calc-modal__title">
|
|||
|
|
<BasicIcon icon="vuesax-linear:chart-2" size="20" class="gold-day-calc-modal__title-icon" />
|
|||
|
|
<span>محاسبهگر طلای شما</span>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<div class="gold-day-calc" dir="rtl">
|
|||
|
|
<BasicInput
|
|||
|
|
v-model:value="weight"
|
|||
|
|
title="وزن طلا (گرم)"
|
|||
|
|
type="tel"
|
|||
|
|
place-holder="مثلاً ۱۰.۵"
|
|||
|
|
inputmode="decimal"
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<BasicInput
|
|||
|
|
:value="unitPriceInput"
|
|||
|
|
title="قیمت روز طلا"
|
|||
|
|
type="tel"
|
|||
|
|
disabled
|
|||
|
|
addon-before="ریال"
|
|||
|
|
place-holder="قیمت صفحه اصلی"
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<BasicInput
|
|||
|
|
v-model:value="feePercent"
|
|||
|
|
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>هزینه طلا</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.taxCost) }}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<template #footer>
|
|||
|
|
<BasicButton type="primary" class="!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'
|
|||
|
|
|
|||
|
|
const visible = goldDayCalculatorVisible
|
|||
|
|
|
|||
|
|
const weight = ref<string | number>('')
|
|||
|
|
const feePercent = 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 tax = parseNum(taxPercent.value)
|
|||
|
|
const unit = liveUnitPrice.value
|
|||
|
|
const goldCost = w * unit
|
|||
|
|
const feeCost = goldCost * (fee / 100)
|
|||
|
|
const taxCost = (goldCost + feeCost) * (tax / 100)
|
|||
|
|
const total = goldCost + feeCost + taxCost
|
|||
|
|
return { goldCost, feeCost, 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__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>
|
|||
|
|
|
|||
|
|
<style lang="less">
|
|||
|
|
.gold-day-calc-modal {
|
|||
|
|
.ant-modal-content {
|
|||
|
|
direction: rtl;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
max-height: min(88dvh, 640px);
|
|||
|
|
overflow: hidden;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ant-modal-header {
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
direction: rtl;
|
|||
|
|
text-align: right;
|
|||
|
|
padding-left: 2.75rem;
|
|||
|
|
padding-right: 1rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ant-modal-title {
|
|||
|
|
text-align: right;
|
|||
|
|
width: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.gold-day-calc-modal__title {
|
|||
|
|
display: inline-flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: flex-start;
|
|||
|
|
gap: 0.5rem;
|
|||
|
|
direction: rtl;
|
|||
|
|
width: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.gold-day-calc-modal__title-icon {
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
color: var(--App-secondary-color, #c9a062);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ant-modal-close {
|
|||
|
|
inset-inline-start: 0 !important;
|
|||
|
|
inset-inline-end: auto !important;
|
|||
|
|
right: auto !important;
|
|||
|
|
left: 0 !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ant-modal-body {
|
|||
|
|
flex: 1 1 auto;
|
|||
|
|
min-height: 0;
|
|||
|
|
overflow: hidden;
|
|||
|
|
padding-bottom: 0.25rem;
|
|||
|
|
max-height: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#body {
|
|||
|
|
max-height: min(48dvh, 360px) !important;
|
|||
|
|
overflow-y: auto !important;
|
|||
|
|
overscroll-behavior: contain;
|
|||
|
|
-webkit-overflow-scrolling: touch;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ant-modal-footer {
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
margin-top: 0;
|
|||
|
|
padding-top: 0.75rem;
|
|||
|
|
border-top: 1px solid rgba(148, 163, 184, 0.25);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media screen and (max-width: 767.98px) {
|
|||
|
|
.gold-day-calc-modal {
|
|||
|
|
.ant-modal {
|
|||
|
|
max-width: calc(100vw - 1.5rem);
|
|||
|
|
margin: 0 auto;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ant-modal-content {
|
|||
|
|
max-height: min(86dvh, 100%);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#body {
|
|||
|
|
max-height: min(42dvh, 280px) !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ant-modal-body {
|
|||
|
|
padding-top: 0.25rem;
|
|||
|
|
padding-bottom: 0.25rem;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media screen and (max-width: 400px) {
|
|||
|
|
.gold-day-calc-modal #body {
|
|||
|
|
max-height: min(38dvh, 240px) !important;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</style>
|