Product Conversion
This commit is contained in:
Vendored
+1
-1
@@ -14,7 +14,6 @@ declare module 'vue' {
|
|||||||
AFormItem: typeof import('ant-design-vue/es')['FormItem']
|
AFormItem: typeof import('ant-design-vue/es')['FormItem']
|
||||||
AImage: typeof import('ant-design-vue/es')['Image']
|
AImage: typeof import('ant-design-vue/es')['Image']
|
||||||
AInput: typeof import('ant-design-vue/es')['Input']
|
AInput: typeof import('ant-design-vue/es')['Input']
|
||||||
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
|
|
||||||
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
||||||
AModal: typeof import('ant-design-vue/es')['Modal']
|
AModal: typeof import('ant-design-vue/es')['Modal']
|
||||||
AppCreditLink: typeof import('./src/components/AppCreditLink.vue')['default']
|
AppCreditLink: typeof import('./src/components/AppCreditLink.vue')['default']
|
||||||
@@ -45,6 +44,7 @@ declare module 'vue' {
|
|||||||
BasicToggleTwoItem: typeof import('./src/components/BasicPacks/BasicToggleTwoItem.vue')['default']
|
BasicToggleTwoItem: typeof import('./src/components/BasicPacks/BasicToggleTwoItem.vue')['default']
|
||||||
Bg_main: typeof import('./src/components/bg_main.vue')['default']
|
Bg_main: typeof import('./src/components/bg_main.vue')['default']
|
||||||
Bordered: typeof import('./src/components/BasicPacks/BaseDatePicker/src/datePick/demo/bordered.vue')['default']
|
Bordered: typeof import('./src/components/BasicPacks/BaseDatePicker/src/datePick/demo/bordered.vue')['default']
|
||||||
|
CopperConvertModal: typeof import('./src/components/CopperConvertModal.vue')['default']
|
||||||
DateRender: typeof import('./src/components/BasicPacks/BaseDatePicker/src/datePick/demo/date-render.vue')['default']
|
DateRender: typeof import('./src/components/BasicPacks/BaseDatePicker/src/datePick/demo/date-render.vue')['default']
|
||||||
Demo: typeof import('./src/components/BasicPacks/BaseDatePicker/src/datePick/demo/index.vue')['default']
|
Demo: typeof import('./src/components/BasicPacks/BaseDatePicker/src/datePick/demo/index.vue')['default']
|
||||||
DialogVant: typeof import('./src/components/DialogVant.vue')['default']
|
DialogVant: typeof import('./src/components/DialogVant.vue')['default']
|
||||||
|
|||||||
@@ -110,6 +110,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<GoldDayCalculatorModal />
|
<GoldDayCalculatorModal />
|
||||||
|
<CopperConvertModal />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@@ -132,6 +133,7 @@ import { usePwaInstall } from '@/composables/usePwaInstall'
|
|||||||
import PwaInstallBanner from '@/components/pwa/PwaInstallBanner.vue'
|
import PwaInstallBanner from '@/components/pwa/PwaInstallBanner.vue'
|
||||||
import PwaInstallGuideModal from '@/components/pwa/PwaInstallGuideModal.vue'
|
import PwaInstallGuideModal from '@/components/pwa/PwaInstallGuideModal.vue'
|
||||||
import GoldDayCalculatorModal from '@/components/GoldDayCalculatorModal.vue'
|
import GoldDayCalculatorModal from '@/components/GoldDayCalculatorModal.vue'
|
||||||
|
import CopperConvertModal from '@/components/CopperConvertModal.vue'
|
||||||
import { useUserStore } from '@/store/userStore'
|
import { useUserStore } from '@/store/userStore'
|
||||||
import { useSettingsStore } from '@/store/settingsStore'
|
import { useSettingsStore } from '@/store/settingsStore'
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
@import (less) '../../views/base/fund/desktop/styles/desktop-fund.less';
|
@import (less) '../../views/base/fund/desktop/styles/desktop-fund.less';
|
||||||
@import (less) '../../views/base/gold/desktop/styles/desktop-gold.less';
|
@import (less) '../../views/base/gold/desktop/styles/desktop-gold.less';
|
||||||
@import (less) './mobile-keyboard-scroll.less';
|
@import (less) './mobile-keyboard-scroll.less';
|
||||||
|
@import (less) '../../styles/app-premium-modal.less';
|
||||||
|
|
||||||
html {
|
html {
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
|
|||||||
@@ -13,10 +13,13 @@
|
|||||||
:width="props.width"
|
:width="props.width"
|
||||||
:ok-text="props.okText"
|
:ok-text="props.okText"
|
||||||
:cancel-text="props.cancelText"
|
:cancel-text="props.cancelText"
|
||||||
:wrapClassName="props.wrapClassName"
|
:wrapClassName="mergedWrapClassName"
|
||||||
:closable="props.closable"
|
:closable="props.closable"
|
||||||
:destroy-on-close="props.destroyOnClose"
|
:destroy-on-close="props.destroyOnClose"
|
||||||
>
|
>
|
||||||
|
<template v-if="$slots.title" #title>
|
||||||
|
<slot name="title" />
|
||||||
|
</template>
|
||||||
<template v-if="props.customFooter" #footer> <slot name="footer" /></template>
|
<template v-if="props.customFooter" #footer> <slot name="footer" /></template>
|
||||||
<a-spin :spinning="props.loading">
|
<a-spin :spinning="props.loading">
|
||||||
<div class="overflow-y-auto py-2 px-1 relative max-h-[83vh]" id="body">
|
<div class="overflow-y-auto py-2 px-1 relative max-h-[83vh]" id="body">
|
||||||
@@ -60,6 +63,11 @@ const props = withDefaults(defineProps<Prop>(), {
|
|||||||
zIndex: APP_Z_MODAL,
|
zIndex: APP_Z_MODAL,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const mergedWrapClassName = computed(() => {
|
||||||
|
const extra = (props.wrapClassName ?? '').trim()
|
||||||
|
return extra ? `app-premium-modal ${extra}` : 'app-premium-modal'
|
||||||
|
})
|
||||||
|
|
||||||
const localValue = computed({
|
const localValue = computed({
|
||||||
get() {
|
get() {
|
||||||
return props.visible
|
return props.visible
|
||||||
@@ -67,13 +75,6 @@ const localValue = computed({
|
|||||||
|
|
||||||
set(value) {
|
set(value) {
|
||||||
emits('update:visible', value)
|
emits('update:visible', value)
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
.ant-modal-content {
|
|
||||||
border-radius: 0.6rem;
|
|
||||||
padding-top: 0.5rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<BasicModal
|
||||||
|
v-model:visible="visible"
|
||||||
|
width="460px"
|
||||||
|
destroy-on-close
|
||||||
|
custom-footer
|
||||||
|
title="تبدیل محصول"
|
||||||
|
wrap-class-name="app-premium-modal--scroll-tall"
|
||||||
|
@close="closeCopperConvert"
|
||||||
|
>
|
||||||
|
<CopperConvertCalculator />
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<BasicButton
|
||||||
|
type="primary"
|
||||||
|
class="app-premium-modal__btn-primary !w-full"
|
||||||
|
size="middle"
|
||||||
|
@click="closeCopperConvert"
|
||||||
|
>
|
||||||
|
بستن
|
||||||
|
</BasicButton>
|
||||||
|
</template>
|
||||||
|
</BasicModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { watch } from 'vue'
|
||||||
|
import BasicModal from '@/components/BasicPacks/BasicModal.vue'
|
||||||
|
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||||
|
import CopperConvertCalculator from '@/views/base/convert/components/CopperConvertCalculator.vue'
|
||||||
|
import {
|
||||||
|
closeCopperConvert,
|
||||||
|
copperConvertModalVisible,
|
||||||
|
} from '@/composables/useCopperConvertModal'
|
||||||
|
import { useCopperConvertPage } from '@/views/base/convert/composables/useCopperConvertPage'
|
||||||
|
|
||||||
|
const visible = copperConvertModalVisible
|
||||||
|
const { refreshLivePrices, stopLivePrices } = useCopperConvertPage()
|
||||||
|
|
||||||
|
watch(visible, async (open) => {
|
||||||
|
if (open) {
|
||||||
|
await refreshLivePrices()
|
||||||
|
} else {
|
||||||
|
stopLivePrices()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -4,11 +4,11 @@
|
|||||||
width="400px"
|
width="400px"
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
custom-footer
|
custom-footer
|
||||||
wrap-class-name="gold-day-calc-modal"
|
wrap-class-name="app-premium-modal--scroll-tall"
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="gold-day-calc-modal__title">
|
<div class="app-premium-modal__title">
|
||||||
<BasicIcon icon="vuesax-linear:calculator" size="20" class="gold-day-calc-modal__title-icon" />
|
<BasicIcon icon="vuesax-linear:calculator" size="20" class="app-premium-modal__title-icon" />
|
||||||
<span>ماشین حساب</span>
|
<span>ماشین حساب</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<BasicButton type="primary" class="!w-full" size="middle" @click="recalculate">
|
<BasicButton type="primary" class="app-premium-modal__btn-primary !w-full" size="middle" @click="recalculate">
|
||||||
محاسبه مجدد
|
محاسبه مجدد
|
||||||
</BasicButton>
|
</BasicButton>
|
||||||
</template>
|
</template>
|
||||||
@@ -219,99 +219,3 @@ watch(visible, async (open) => {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
</style>
|
</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>
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
export const copperConvertModalVisible = ref(false)
|
||||||
|
|
||||||
|
export function openCopperConvert() {
|
||||||
|
copperConvertModalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
export function closeCopperConvert() {
|
||||||
|
copperConvertModalVisible.value = false
|
||||||
|
}
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
/** تم یکسان مدالهای boursecu user — لوکس / پریمیوم */
|
||||||
|
|
||||||
|
.app-premium-modal.ant-modal-wrap {
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-premium-modal {
|
||||||
|
.ant-modal-content {
|
||||||
|
direction: rtl;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: min(92dvh, 720px);
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0 !important;
|
||||||
|
border-radius: 1.125rem;
|
||||||
|
border: 1px solid rgba(201, 160, 98, 0.35);
|
||||||
|
box-shadow:
|
||||||
|
0 24px 48px rgba(15, 39, 68, 0.28),
|
||||||
|
0 0 0 1px rgba(255, 255, 255, 0.04) inset;
|
||||||
|
background: #fff;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-header {
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
margin: 0;
|
||||||
|
min-height: 3.25rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding: 0.875rem 1rem 0.875rem 3rem;
|
||||||
|
direction: rtl;
|
||||||
|
text-align: right;
|
||||||
|
background: linear-gradient(180deg, #faf8f5 0%, #fff 100%);
|
||||||
|
border-bottom: 1px solid rgba(201, 160, 98, 0.2);
|
||||||
|
border-radius: 1.125rem 1.125rem 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-title {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1.45;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 1.0625rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #0f2744;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-premium-modal__title {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 0.5rem;
|
||||||
|
direction: rtl;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-premium-modal__title-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: var(--App-secondary-color, #c9a062);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-close {
|
||||||
|
position: absolute;
|
||||||
|
inset-inline-start: 0.75rem !important;
|
||||||
|
inset-inline-end: auto !important;
|
||||||
|
left: 0.75rem !important;
|
||||||
|
right: auto !important;
|
||||||
|
top: 0.875rem !important;
|
||||||
|
margin: 0;
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(15, 39, 68, 0.06);
|
||||||
|
color: #0f2744;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(15, 39, 68, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-close-x {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
line-height: 1;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-body {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 0.75rem 1.125rem 0.5rem;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#body {
|
||||||
|
max-height: min(56dvh, 440px);
|
||||||
|
overflow-y: auto !important;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
padding: 0.35rem 0.125rem 0.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-footer {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.75rem 1.125rem 1rem;
|
||||||
|
border-top: 1px solid rgba(15, 39, 68, 0.08);
|
||||||
|
background: #faf8f5;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-footer .ant-btn {
|
||||||
|
min-height: 2.5rem;
|
||||||
|
border-radius: 0.75rem !important;
|
||||||
|
font-weight: 700;
|
||||||
|
padding-inline: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-footer .ant-btn-primary,
|
||||||
|
.app-premium-modal__btn-primary {
|
||||||
|
border: none !important;
|
||||||
|
background: linear-gradient(135deg, #0f2744 0%, #1a365d 55%, #234876 100%) !important;
|
||||||
|
box-shadow: 0 8px 20px rgba(15, 39, 68, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-footer .ant-btn-default {
|
||||||
|
border-color: rgba(15, 39, 68, 0.15) !important;
|
||||||
|
color: #0f2744 !important;
|
||||||
|
background: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.app-premium-modal--no-header {
|
||||||
|
.ant-modal-header {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-close {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-body {
|
||||||
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.app-premium-modal--scroll-tall #body {
|
||||||
|
max-height: min(58dvh, 480px) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.app-premium-modal--fluid-body #body {
|
||||||
|
max-height: none !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
padding-top: 0 !important;
|
||||||
|
padding-bottom: 0.25rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.app-premium-modal--wide-panel {
|
||||||
|
.ant-modal-body {
|
||||||
|
padding-inline: 1.5rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#body {
|
||||||
|
max-height: calc(100dvh - 12rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 767.98px) {
|
||||||
|
.app-premium-modal {
|
||||||
|
.ant-modal {
|
||||||
|
max-width: calc(100vw - 1rem);
|
||||||
|
margin: 0 auto;
|
||||||
|
top: 1.5vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.app-premium-modal--scroll-tall #body {
|
||||||
|
max-height: min(52dvh, 420px) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/** فرمول تبدیل کارفرما — اکسل «تبدیل کاتد و مفتول به سیم ۱.۵–۲.۵» */
|
||||||
|
|
||||||
|
export type CopperConvertSource = 'katod' | 'maftoul' | 'shamsh'
|
||||||
|
|
||||||
|
export interface CopperConvertPrices {
|
||||||
|
katodPerKg: number
|
||||||
|
maftoulPerKg: number
|
||||||
|
shamshPerKg: number
|
||||||
|
/** قیمت هر حلقه سیم ۱.۵ (ریال) */
|
||||||
|
wireRing15: number
|
||||||
|
/** قیمت هر حلقه سیم ۲.۵ (ریال) */
|
||||||
|
wireRing25: number
|
||||||
|
/** وزن هر حلقه ۱.۵ — گرم (ستون اکسل) */
|
||||||
|
wireWeightGrams15: number
|
||||||
|
/** وزن هر حلقه ۲.۵ — گرم؛ اگر صفر باشد فقط محاسبه ارزشی */
|
||||||
|
wireWeightGrams25: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CopperConvertResult {
|
||||||
|
totalValueRial: number
|
||||||
|
equivalentMaftoulKg: number
|
||||||
|
/** ارزش ÷ قیمت حلقه (شیت مفتول→سیم) */
|
||||||
|
rings15ByValue: number
|
||||||
|
rings25ByValue: number
|
||||||
|
/** مفتول معادل ÷ وزن حلقه */
|
||||||
|
rings15ByWeight: number | null
|
||||||
|
rings25ByWeight: number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** مقادیر نمونه اکسل — وقتی محصول سیم در لیست نیست */
|
||||||
|
export const COPPER_CONVERT_WIRE_FALLBACK = {
|
||||||
|
wireRing15: 36_000_000,
|
||||||
|
wireRing25: 38_000_000,
|
||||||
|
wireWeightGrams15: 2777,
|
||||||
|
wireWeightGrams25: 0,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
function sourcePricePerKg(source: CopperConvertSource, prices: CopperConvertPrices): number {
|
||||||
|
if (source === 'katod') return prices.katodPerKg
|
||||||
|
if (source === 'maftoul') return prices.maftoulPerKg
|
||||||
|
return prices.shamshPerKg
|
||||||
|
}
|
||||||
|
|
||||||
|
function ringsByWeight(maftoulKg: number, gramsPerRing: number): number | null {
|
||||||
|
if (maftoulKg <= 0 || gramsPerRing <= 0) return null
|
||||||
|
return (maftoulKg * 1000) / gramsPerRing
|
||||||
|
}
|
||||||
|
|
||||||
|
export function calcCopperWireConvert(
|
||||||
|
inputKg: number,
|
||||||
|
source: CopperConvertSource,
|
||||||
|
prices: CopperConvertPrices,
|
||||||
|
): CopperConvertResult | null {
|
||||||
|
if (!Number.isFinite(inputKg) || inputKg <= 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const perKg = sourcePricePerKg(source, prices)
|
||||||
|
if (perKg <= 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalValueRial = inputKg * perKg
|
||||||
|
const equivalentMaftoulKg =
|
||||||
|
prices.maftoulPerKg > 0 ? totalValueRial / prices.maftoulPerKg : 0
|
||||||
|
|
||||||
|
const rings15ByValue = prices.wireRing15 > 0 ? totalValueRial / prices.wireRing15 : 0
|
||||||
|
const rings25ByValue = prices.wireRing25 > 0 ? totalValueRial / prices.wireRing25 : 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalValueRial,
|
||||||
|
equivalentMaftoulKg,
|
||||||
|
rings15ByValue,
|
||||||
|
rings25ByValue,
|
||||||
|
rings15ByWeight: ringsByWeight(equivalentMaftoulKg, prices.wireWeightGrams15),
|
||||||
|
rings25ByWeight: ringsByWeight(equivalentMaftoulKg, prices.wireWeightGrams25),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { openCopperConvert } from '@/composables/useCopperConvertModal'
|
||||||
import { getBoursecuActionIconSrc } from '@/utils/config'
|
import { getBoursecuActionIconSrc } from '@/utils/config'
|
||||||
import type { HomeQuickActionItem } from '../types'
|
import type { HomeQuickActionItem } from '../types'
|
||||||
|
|
||||||
@@ -42,6 +43,6 @@ export const boursecuQuickActions: HomeQuickActionItem[] = [
|
|||||||
label: 'تبدیل',
|
label: 'تبدیل',
|
||||||
icon: 'vuesax-linear:refresh-circle',
|
icon: 'vuesax-linear:refresh-circle',
|
||||||
iconSrc: getBoursecuActionIconSrc('convert'),
|
iconSrc: getBoursecuActionIconSrc('convert'),
|
||||||
route: { name: 'buyProduct' },
|
handler: () => openCopperConvert(),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -138,14 +138,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dproducts-order-panel {
|
.dproducts-order-panel {
|
||||||
:deep(.ant-modal-content) {
|
|
||||||
border-radius: 16px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.ant-modal-body) {
|
:deep(.ant-modal-body) {
|
||||||
max-height: calc(100vh - 180px);
|
max-height: calc(100vh - 180px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 0 24px 24px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<BasicModal
|
<BasicModal
|
||||||
v-else
|
v-else
|
||||||
v-model:visible="showFilter"
|
v-model:visible="showFilter"
|
||||||
wrap-class-name="dproducts-order-panel"
|
wrap-class-name="dproducts-order-panel app-premium-modal--wide-panel"
|
||||||
:title="selectedProduct?.title ?? 'ثبت سفارش'"
|
:title="selectedProduct?.title ?? 'ثبت سفارش'"
|
||||||
width="560px"
|
width="560px"
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
width="440px"
|
width="440px"
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
custom-footer
|
custom-footer
|
||||||
wrap-class-name="user-edit-modal"
|
wrap-class-name="app-premium-modal--fluid-body"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
>
|
>
|
||||||
<div class="user-edit-modal__status" :class="isLegal.color">
|
<div class="user-edit-modal__status" :class="isLegal.color">
|
||||||
@@ -112,36 +112,6 @@ const {
|
|||||||
color: #16a34a;
|
color: #16a34a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-edit-modal {
|
|
||||||
.ant-modal-content {
|
|
||||||
border-radius: 16px;
|
|
||||||
padding-top: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-modal-header {
|
|
||||||
margin-bottom: 0;
|
|
||||||
padding-bottom: 0.75rem;
|
|
||||||
border-bottom: 1px solid #f1f5f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-modal-title {
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #0f172a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-modal-body {
|
|
||||||
padding-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
#body {
|
|
||||||
max-height: none;
|
|
||||||
overflow: visible;
|
|
||||||
padding-top: 0;
|
|
||||||
padding-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-edit-modal__status {
|
.user-edit-modal__status {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
v-model:visible="localVisible"
|
v-model:visible="localVisible"
|
||||||
custom-footer
|
custom-footer
|
||||||
:closable="false"
|
:closable="false"
|
||||||
wrap-class-name="deposit-guide-modal-wrap"
|
wrap-class-name="app-premium-modal--no-header"
|
||||||
width="min(92vw, 380px)"
|
width="min(92vw, 380px)"
|
||||||
>
|
>
|
||||||
<div class="deposit-guide">
|
<div class="deposit-guide">
|
||||||
@@ -102,7 +102,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<BasicButton type="primary" class="deposit-guide__confirm w-full" @click="localVisible = false">
|
<BasicButton
|
||||||
|
type="primary"
|
||||||
|
class="app-premium-modal__btn-primary deposit-guide__confirm w-full"
|
||||||
|
@click="localVisible = false"
|
||||||
|
>
|
||||||
متوجه شدم
|
متوجه شدم
|
||||||
</BasicButton>
|
</BasicButton>
|
||||||
</template>
|
</template>
|
||||||
@@ -305,20 +309,3 @@ watch(
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="less">
|
|
||||||
.deposit-guide-modal-wrap {
|
|
||||||
.ant-modal-content {
|
|
||||||
border-radius: 1rem;
|
|
||||||
padding-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-modal-header {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-modal-footer {
|
|
||||||
border-top: none;
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
Reference in New Issue
Block a user