Product Conversion
This commit is contained in:
@@ -0,0 +1,507 @@
|
||||
<template>
|
||||
<div class="copper-convert" dir="rtl">
|
||||
<p class="copper-convert__lead">
|
||||
وزن دارایی را وارد کنید؛ معادل مفتول و تعداد حلقه سیم ۱.۵ و ۲.۵ با قیمت روز محاسبه میشود.
|
||||
</p>
|
||||
|
||||
<a-spin :spinning="loading">
|
||||
<section class="copper-convert__panel" aria-label="قیمتهای مبنا">
|
||||
<div class="copper-convert__panel-head">
|
||||
<span class="copper-convert__panel-kicker">قیمت لحظهای</span>
|
||||
<span class="copper-convert__panel-title">مبنای محاسبه</span>
|
||||
</div>
|
||||
|
||||
<div class="copper-convert__price-group">
|
||||
<span class="copper-convert__group-label">مواد اولیه (هر کیلوگرم)</span>
|
||||
<ul class="copper-convert__price-grid m-0 p-0 list-none">
|
||||
<li
|
||||
v-for="row in metalPriceRows"
|
||||
:key="row.label"
|
||||
class="copper-convert__chip"
|
||||
:class="{ 'copper-convert__chip--muted': row.missing }"
|
||||
>
|
||||
<span class="copper-convert__chip-label">{{ row.label }}</span>
|
||||
<span class="copper-convert__chip-value" dir="ltr">{{ row.value }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="copper-convert__price-group copper-convert__price-group--wire">
|
||||
<span class="copper-convert__group-label">سیم (هر حلقه)</span>
|
||||
<ul class="copper-convert__price-grid copper-convert__price-grid--wire m-0 p-0 list-none">
|
||||
<li
|
||||
v-for="row in wirePriceRows"
|
||||
:key="row.label"
|
||||
class="copper-convert__chip copper-convert__chip--accent"
|
||||
>
|
||||
<span class="copper-convert__chip-label">{{ row.label }}</span>
|
||||
<span class="copper-convert__chip-value" dir="ltr">{{ row.value }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="copper-convert__form">
|
||||
<label class="copper-convert__field-label">نوع دارایی</label>
|
||||
<div class="copper-convert__pills" role="tablist">
|
||||
<button
|
||||
v-for="opt in sourceOptions"
|
||||
:key="opt.value"
|
||||
type="button"
|
||||
role="tab"
|
||||
class="copper-convert__pill"
|
||||
:class="{ 'copper-convert__pill--active': source === opt.value }"
|
||||
:aria-selected="source === opt.value"
|
||||
@click="source = opt.value"
|
||||
>
|
||||
{{ opt.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="copper-convert__weight">
|
||||
<label class="copper-convert__field-label" for="copper-convert-kg">مقدار</label>
|
||||
<div class="copper-convert__weight-wrap">
|
||||
<input
|
||||
id="copper-convert-kg"
|
||||
v-model="inputKg"
|
||||
class="copper-convert__weight-input"
|
||||
type="text"
|
||||
inputmode="decimal"
|
||||
placeholder="مثلاً ۱۰"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<span class="copper-convert__weight-unit">کیلوگرم</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="result" class="copper-convert__result">
|
||||
<div class="copper-convert__result-head">
|
||||
<span class="copper-convert__result-kicker">خروجی</span>
|
||||
<span class="copper-convert__result-title">نتیجه تبدیل</span>
|
||||
</div>
|
||||
|
||||
<div class="copper-convert__rings">
|
||||
<article class="copper-convert__ring-card">
|
||||
<span class="copper-convert__ring-badge">۱.۵ mm²</span>
|
||||
<p class="copper-convert__ring-count" dir="ltr">{{ formatRings(result.rings15ByValue) }}</p>
|
||||
<p class="copper-convert__ring-caption">حلقه سیم</p>
|
||||
<p
|
||||
v-if="result.rings15ByWeight != null"
|
||||
class="copper-convert__ring-sub"
|
||||
dir="ltr"
|
||||
>
|
||||
≈ {{ formatRings(result.rings15ByWeight) }} بر اساس وزن حلقه
|
||||
</p>
|
||||
</article>
|
||||
<article class="copper-convert__ring-card">
|
||||
<span class="copper-convert__ring-badge">۲.۵ mm²</span>
|
||||
<p class="copper-convert__ring-count" dir="ltr">{{ formatRings(result.rings25ByValue) }}</p>
|
||||
<p class="copper-convert__ring-caption">حلقه سیم</p>
|
||||
<p
|
||||
v-if="result.rings25ByWeight != null"
|
||||
class="copper-convert__ring-sub"
|
||||
dir="ltr"
|
||||
>
|
||||
≈ {{ formatRings(result.rings25ByWeight) }} بر اساس وزن حلقه
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="copper-convert__summary">
|
||||
<div class="copper-convert__summary-row">
|
||||
<span>ارزش معادل</span>
|
||||
<strong dir="ltr">{{ formatRial(result.totalValueRial) }} <small>ریال</small></strong>
|
||||
</div>
|
||||
<div class="copper-convert__summary-row">
|
||||
<span>معادل مفتول</span>
|
||||
<strong dir="ltr">{{ formatKg(result.equivalentMaftoulKg) }} <small>کیلو</small></strong>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<p v-else-if="inputKg && !loading" class="copper-convert__warn">
|
||||
مقدار معتبر وارد کنید و از فعال بودن قیمت کاتد/مفتول در سامانه مطمئن شوید.
|
||||
</p>
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useCopperConvertPageContext } from '../composables/useCopperConvertPage'
|
||||
import type { CopperConvertSource } from '@/utils/copperWireConvert'
|
||||
|
||||
const { loading, source, inputKg, result, priceRows } = useCopperConvertPageContext()
|
||||
|
||||
const sourceOptions = computed(() => [
|
||||
{ label: 'کاتد', value: 'katod' as CopperConvertSource },
|
||||
{ label: 'مفتول', value: 'maftoul' as CopperConvertSource },
|
||||
{ label: 'شمش', value: 'shamsh' as CopperConvertSource },
|
||||
])
|
||||
|
||||
const metalPriceRows = computed(() => priceRows.value.slice(0, 3))
|
||||
const wirePriceRows = computed(() => priceRows.value.slice(3))
|
||||
|
||||
function formatRial(value: number) {
|
||||
return Math.round(value).toLocaleString('en-US')
|
||||
}
|
||||
|
||||
function formatKg(value: number) {
|
||||
if (value >= 100) return value.toFixed(1)
|
||||
if (value >= 10) return value.toFixed(2)
|
||||
return value.toFixed(3)
|
||||
}
|
||||
|
||||
function formatRings(value: number) {
|
||||
if (value >= 100) return value.toFixed(1)
|
||||
if (value >= 10) return value.toFixed(2)
|
||||
return value.toFixed(3)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.copper-convert {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.copper-convert__lead {
|
||||
margin: 0 0 0.125rem;
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.65;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.copper-convert__panel {
|
||||
position: relative;
|
||||
padding: 0.75rem 0.875rem 0.7rem;
|
||||
border-radius: 1rem;
|
||||
background: linear-gradient(145deg, #0f2744 0%, #152f4f 48%, #1a365d 100%);
|
||||
border: 1px solid rgba(201, 160, 98, 0.35);
|
||||
box-shadow:
|
||||
0 12px 28px rgba(15, 39, 68, 0.22),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.06);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.copper-convert__panel::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -40px;
|
||||
inset-inline-end: -30px;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(232, 180, 140, 0.28) 0%, transparent 70%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.copper-convert__panel-head {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.copper-convert__panel-kicker {
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
color: rgba(201, 160, 98, 0.95);
|
||||
}
|
||||
|
||||
.copper-convert__panel-title {
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 800;
|
||||
color: #f8fafc;
|
||||
}
|
||||
|
||||
.copper-convert__price-group {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-bottom: 0.65rem;
|
||||
}
|
||||
|
||||
.copper-convert__price-group--wire {
|
||||
margin-bottom: 0;
|
||||
padding-top: 0.65rem;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.copper-convert__group-label {
|
||||
display: block;
|
||||
margin-bottom: 0.45rem;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
color: rgba(226, 232, 240, 0.75);
|
||||
}
|
||||
|
||||
.copper-convert__price-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.copper-convert__price-grid--wire {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.copper-convert__chip {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
min-width: 0;
|
||||
padding: 0.45rem 0.5rem;
|
||||
border-radius: 0.65rem;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.copper-convert__chip--accent {
|
||||
background: rgba(201, 160, 98, 0.12);
|
||||
border-color: rgba(201, 160, 98, 0.35);
|
||||
}
|
||||
|
||||
.copper-convert__chip--muted .copper-convert__chip-value {
|
||||
color: rgba(248, 250, 252, 0.45);
|
||||
}
|
||||
|
||||
.copper-convert__chip-label {
|
||||
font-size: 0.625rem;
|
||||
line-height: 1.45;
|
||||
color: rgba(226, 232, 240, 0.85);
|
||||
}
|
||||
|
||||
.copper-convert__chip-value {
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 700;
|
||||
color: #f8fafc;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.copper-convert__form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.65rem;
|
||||
margin-top: 0.125rem;
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 1rem;
|
||||
background: #faf8f5;
|
||||
border: 1px solid rgba(201, 160, 98, 0.22);
|
||||
}
|
||||
|
||||
.copper-convert__field-label {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.copper-convert__pills {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 0.35rem;
|
||||
padding: 0.25rem;
|
||||
border-radius: 999px;
|
||||
background: #eef2f6;
|
||||
}
|
||||
|
||||
.copper-convert__pill {
|
||||
appearance: none;
|
||||
border: none;
|
||||
border-radius: 999px;
|
||||
padding: 0.5rem 0.35rem;
|
||||
font-family: inherit;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
color: #64748b;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.copper-convert__pill--active {
|
||||
color: #0f2744;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px rgba(15, 39, 68, 0.12);
|
||||
}
|
||||
|
||||
.copper-convert__weight-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.35rem;
|
||||
padding: 0.55rem 0.75rem;
|
||||
border-radius: 0.75rem;
|
||||
background: #fff;
|
||||
border: 1px solid rgba(15, 39, 68, 0.12);
|
||||
box-shadow: inset 0 1px 2px rgba(15, 39, 68, 0.04);
|
||||
}
|
||||
|
||||
.copper-convert__weight-input {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 800;
|
||||
color: #0f2744;
|
||||
text-align: right;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.copper-convert__weight-input::placeholder {
|
||||
color: #94a3b8;
|
||||
font-weight: 500;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.copper-convert__weight-unit {
|
||||
flex-shrink: 0;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 800;
|
||||
color: #c9a062;
|
||||
}
|
||||
|
||||
.copper-convert__result {
|
||||
margin-top: 0.125rem;
|
||||
padding: 0.875rem;
|
||||
border-radius: 1rem;
|
||||
background: linear-gradient(180deg, #fff 0%, #faf8f5 100%);
|
||||
border: 1px solid rgba(201, 160, 98, 0.35);
|
||||
box-shadow: 0 8px 24px rgba(15, 39, 68, 0.08);
|
||||
}
|
||||
|
||||
.copper-convert__result-head {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.1rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.copper-convert__result-kicker {
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 700;
|
||||
color: #c9a062;
|
||||
}
|
||||
|
||||
.copper-convert__result-title {
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 800;
|
||||
color: #0f2744;
|
||||
}
|
||||
|
||||
.copper-convert__rings {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.copper-convert__ring-card {
|
||||
position: relative;
|
||||
padding: 0.75rem 0.5rem;
|
||||
border-radius: 0.85rem;
|
||||
text-align: center;
|
||||
background: linear-gradient(160deg, #0f2744 0%, #1a365d 100%);
|
||||
border: 1px solid rgba(201, 160, 98, 0.4);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.copper-convert__ring-card::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset-inline-start: 50%;
|
||||
bottom: -24px;
|
||||
width: 80px;
|
||||
height: 48px;
|
||||
transform: translateX(-50%);
|
||||
background: radial-gradient(ellipse, rgba(201, 160, 98, 0.35) 0%, transparent 70%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.copper-convert__ring-badge {
|
||||
display: inline-block;
|
||||
margin-bottom: 0.35rem;
|
||||
padding: 0.15rem 0.45rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.625rem;
|
||||
font-weight: 700;
|
||||
color: #0f2744;
|
||||
background: rgba(201, 160, 98, 0.95);
|
||||
}
|
||||
|
||||
.copper-convert__ring-count {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 800;
|
||||
line-height: 1.2;
|
||||
color: #f8fafc;
|
||||
}
|
||||
|
||||
.copper-convert__ring-caption {
|
||||
margin: 0.15rem 0 0;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
color: rgba(226, 232, 240, 0.85);
|
||||
}
|
||||
|
||||
.copper-convert__ring-sub {
|
||||
margin: 0.35rem 0 0;
|
||||
font-size: 0.625rem;
|
||||
color: rgba(201, 160, 98, 0.9);
|
||||
}
|
||||
|
||||
.copper-convert__summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.45rem;
|
||||
padding-top: 0.65rem;
|
||||
border-top: 1px dashed rgba(15, 39, 68, 0.15);
|
||||
}
|
||||
|
||||
.copper-convert__summary-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.copper-convert__summary-row strong {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 800;
|
||||
color: #0f2744;
|
||||
}
|
||||
|
||||
.copper-convert__summary-row strong small {
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.copper-convert__warn {
|
||||
margin: 0;
|
||||
padding: 0.65rem 0.75rem;
|
||||
border-radius: 0.75rem;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.6;
|
||||
color: #9a3412;
|
||||
background: #fff7ed;
|
||||
border: 1px solid rgba(234, 88, 12, 0.2);
|
||||
}
|
||||
|
||||
@media (max-width: 380px) {
|
||||
.copper-convert__price-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,175 @@
|
||||
import {
|
||||
computed,
|
||||
inject,
|
||||
provide,
|
||||
ref,
|
||||
type ComputedRef,
|
||||
type InjectionKey,
|
||||
type Ref,
|
||||
} from 'vue'
|
||||
import { getProducts } from '@/api/products'
|
||||
import type { Product } from '@/models/products'
|
||||
import {
|
||||
COPPER_CONVERT_WIRE_FALLBACK,
|
||||
calcCopperWireConvert,
|
||||
type CopperConvertPrices,
|
||||
type CopperConvertResult,
|
||||
type CopperConvertSource,
|
||||
} from '@/utils/copperWireConvert'
|
||||
import { LEGACY_GROUP_KATOD, LEGACY_GROUP_MAFTOL, normalizeProductGroupType } from '@/utils/productCategories'
|
||||
import { getProductBuyPrice } from '@/utils/productPrice'
|
||||
import { getLastPrices, startLastPricePolling, stopLastPricePolling } from '@/utils/price'
|
||||
import { persianToEnglishDigits } from '@/utils/persianDigits'
|
||||
|
||||
export const COPPER_CONVERT_PAGE_KEY: InjectionKey<CopperConvertPageContext> =
|
||||
Symbol('copperConvertPageContext')
|
||||
|
||||
export interface CopperConvertPageContext {
|
||||
loading: Ref<boolean>
|
||||
source: Ref<CopperConvertSource>
|
||||
inputKg: Ref<string>
|
||||
prices: ComputedRef<CopperConvertPrices>
|
||||
result: ComputedRef<CopperConvertResult | null>
|
||||
priceRows: ComputedRef<{ label: string; value: string; missing?: boolean }[]>
|
||||
}
|
||||
|
||||
function parseKg(raw: string): number {
|
||||
const s = persianToEnglishDigits(String(raw ?? ''))
|
||||
.replace(/,/g, '')
|
||||
.replace(/[^\d.]/g, '')
|
||||
const n = Number.parseFloat(s)
|
||||
return Number.isFinite(n) ? n : 0
|
||||
}
|
||||
|
||||
function titleIncludes(product: Product, part: string) {
|
||||
return (product.title ?? '').includes(part)
|
||||
}
|
||||
|
||||
function isKatodProduct(product: Product) {
|
||||
const group = normalizeProductGroupType(product.group_type)
|
||||
return group === LEGACY_GROUP_KATOD || titleIncludes(product, 'کاتد')
|
||||
}
|
||||
|
||||
function isMaftolProduct(product: Product) {
|
||||
const group = normalizeProductGroupType(product.group_type)
|
||||
return group === LEGACY_GROUP_MAFTOL || titleIncludes(product, 'مفتول')
|
||||
}
|
||||
|
||||
function isShamshProduct(product: Product) {
|
||||
const group = normalizeProductGroupType(product.group_type)
|
||||
return group.includes('شمش') || titleIncludes(product, 'شمش')
|
||||
}
|
||||
|
||||
function wireCrossSection(product: Product): '1.5' | '2.5' | null {
|
||||
const t = product.title ?? ''
|
||||
if (/1\.5|۱\.۵/.test(t)) return '1.5'
|
||||
if (/2\.5|۲\.۵/.test(t)) return '2.5'
|
||||
return null
|
||||
}
|
||||
|
||||
function isWireProduct(product: Product) {
|
||||
const t = `${product.group_type ?? ''} ${product.title ?? ''}`
|
||||
return t.includes('سیم') || wireCrossSection(product) != null
|
||||
}
|
||||
|
||||
function pickProduct(
|
||||
products: Product[],
|
||||
matcher: (p: Product) => boolean,
|
||||
): Product | undefined {
|
||||
return products.find((p) => p.active && matcher(p))
|
||||
}
|
||||
|
||||
function resolveWireRingPrice(product: Product | undefined, fallback: number): number {
|
||||
if (!product) return fallback
|
||||
const price = getProductBuyPrice(product)
|
||||
return price > 0 ? price : fallback
|
||||
}
|
||||
|
||||
export function useCopperConvertPage() {
|
||||
const loading = ref(true)
|
||||
const source = ref<CopperConvertSource>('katod')
|
||||
const inputKg = ref('')
|
||||
const catalog = ref<Product[]>([])
|
||||
|
||||
const katodProduct = computed(() => pickProduct(catalog.value, isKatodProduct))
|
||||
const maftolProduct = computed(() => pickProduct(catalog.value, isMaftolProduct))
|
||||
const shamshProduct = computed(() => pickProduct(catalog.value, isShamshProduct))
|
||||
const wire15Product = computed(() =>
|
||||
pickProduct(catalog.value, (p) => isWireProduct(p) && wireCrossSection(p) === '1.5'),
|
||||
)
|
||||
const wire25Product = computed(() =>
|
||||
pickProduct(catalog.value, (p) => isWireProduct(p) && wireCrossSection(p) === '2.5'),
|
||||
)
|
||||
|
||||
const prices = computed<CopperConvertPrices>(() => ({
|
||||
katodPerKg: katodProduct.value ? getProductBuyPrice(katodProduct.value) : 0,
|
||||
maftoulPerKg: maftolProduct.value ? getProductBuyPrice(maftolProduct.value) : 0,
|
||||
shamshPerKg: shamshProduct.value ? getProductBuyPrice(shamshProduct.value) : 0,
|
||||
wireRing15: resolveWireRingPrice(wire15Product.value, COPPER_CONVERT_WIRE_FALLBACK.wireRing15),
|
||||
wireRing25: resolveWireRingPrice(wire25Product.value, COPPER_CONVERT_WIRE_FALLBACK.wireRing25),
|
||||
wireWeightGrams15: COPPER_CONVERT_WIRE_FALLBACK.wireWeightGrams15,
|
||||
wireWeightGrams25: COPPER_CONVERT_WIRE_FALLBACK.wireWeightGrams25,
|
||||
}))
|
||||
|
||||
const result = computed(() =>
|
||||
calcCopperWireConvert(parseKg(inputKg.value), source.value, prices.value),
|
||||
)
|
||||
|
||||
const priceRows = computed(() => {
|
||||
const p = prices.value
|
||||
const row = (label: string, amount: number) => ({
|
||||
label,
|
||||
value: amount > 0 ? `${Math.round(amount).toLocaleString('en-US')} ریال` : '—',
|
||||
missing: amount <= 0,
|
||||
})
|
||||
return [
|
||||
row('قیمت کاتد (هر کیلو)', p.katodPerKg),
|
||||
row('قیمت مفتول (هر کیلو)', p.maftoulPerKg),
|
||||
row('قیمت شمش (هر کیلو)', p.shamshPerKg),
|
||||
row('قیمت حلقه سیم ۱.۵', p.wireRing15),
|
||||
row('قیمت حلقه سیم ۲.۵', p.wireRing25),
|
||||
]
|
||||
})
|
||||
|
||||
async function loadCatalog() {
|
||||
loading.value = true
|
||||
try {
|
||||
await getLastPrices()
|
||||
const list = await getProducts()
|
||||
catalog.value = Array.isArray(list) ? list : []
|
||||
} catch {
|
||||
catalog.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshLivePrices() {
|
||||
startLastPricePolling()
|
||||
await loadCatalog()
|
||||
}
|
||||
|
||||
function stopLivePrices() {
|
||||
stopLastPricePolling()
|
||||
}
|
||||
|
||||
const ctx: CopperConvertPageContext = {
|
||||
loading,
|
||||
source,
|
||||
inputKg,
|
||||
prices,
|
||||
result,
|
||||
priceRows,
|
||||
}
|
||||
|
||||
provide(COPPER_CONVERT_PAGE_KEY, ctx)
|
||||
return { ...ctx, refreshLivePrices, stopLivePrices, loadCatalog }
|
||||
}
|
||||
|
||||
export function useCopperConvertPageContext() {
|
||||
const ctx = inject(COPPER_CONVERT_PAGE_KEY)
|
||||
if (!ctx) {
|
||||
throw new Error('useCopperConvertPageContext requires useCopperConvertPage')
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
Reference in New Issue
Block a user