feat(convert,assets): convert page route and assets portfolio chart
Add convert mobile/desktop views and assets chart from price history; polish calculator and wallet/home integration. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
فرمت هر خط:
|
فرمت هر خط:
|
||||||
- YYYY-MM-DD | موضوع | مسیر فایل(ها) | نکتهٔ ۱ خطی
|
- YYYY-MM-DD | موضوع | مسیر فایل(ها) | نکتهٔ ۱ خطی
|
||||||
|
|
||||||
|
- 2026-10-22 | addon اینپوت | `assets/css/main.less` | پسزمینه addon = secondary تم، نه primary (سبز tenant)
|
||||||
- 2026-10-21 | نمودار خانه = لندینگ | `LandingLiveChartMain.vue` + `useLandingLiveChart` + `zaarboxPriceHistory`؛ جایگزین `PriceChart` در PriceChartSection/DesktopChart* | همان `dlanding-live__main` SVG؛ داده از zaarbox + `/api/price/last`
|
- 2026-10-21 | نمودار خانه = لندینگ | `LandingLiveChartMain.vue` + `useLandingLiveChart` + `zaarboxPriceHistory`؛ جایگزین `PriceChart` در PriceChartSection/DesktopChart* | همان `dlanding-live__main` SVG؛ داده از zaarbox + `/api/price/last`
|
||||||
- 2026-10-20 | ظاهر خرید اقساطی | `views/base/installment/InstallmentPanel.vue` | دسکتاپ کلاسهای `dgift` مثل کارت هدیه؛ موبایل کارت `rounded-xl` و توضیح بالای فرم
|
- 2026-10-20 | ظاهر خرید اقساطی | `views/base/installment/InstallmentPanel.vue` | دسکتاپ کلاسهای `dgift` مثل کارت هدیه؛ موبایل کارت `rounded-xl` و توضیح بالای فرم
|
||||||
- 2026-10-20 | خرید اقساطی دیجیپی | سرور `DigipayInstallmentService` + تنظیمات `enable_digipay_installment`/`digipay_*`؛ اپ `views/base/installment`؛ ادمین `customers/digipayInstallment` | لینک احراز در تنظیمات؛ مانده اعتبار را ادمین با موبایل مینویسد؛ خرید مس را به کیف gold مینشاند
|
- 2026-10-20 | خرید اقساطی دیجیپی | سرور `DigipayInstallmentService` + تنظیمات `enable_digipay_installment`/`digipay_*`؛ اپ `views/base/installment`؛ ادمین `customers/digipayInstallment` | لینک احراز در تنظیمات؛ مانده اعتبار را ادمین با موبایل مینویسد؛ خرید مس را به کیف gold مینشاند
|
||||||
|
|||||||
@@ -110,7 +110,6 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<GoldDayCalculatorModal />
|
<GoldDayCalculatorModal />
|
||||||
<CopperConvertModal />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@@ -133,7 +132,6 @@ 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 {
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ html.project-boursecu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-input-group-addon {
|
.ant-input-group-addon {
|
||||||
background-color: @secondary-color !important;
|
background-color: #e0be7f !important;
|
||||||
color: @App-primary-color !important;
|
color: #0a1b37 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-red,
|
.text-red,
|
||||||
|
|||||||
@@ -451,8 +451,8 @@ body:has(.ant-modal-root .ant-modal-wrap:not([style*='display: none'])) {
|
|||||||
color:@App-primary-color !important;
|
color:@App-primary-color !important;
|
||||||
}
|
}
|
||||||
.ant-input-group-addon{
|
.ant-input-group-addon{
|
||||||
background-color: @App-primary-color !important;
|
background-color: #e0be7f !important;
|
||||||
color: @secondary-color !important;
|
color: #0a1b37 !important;
|
||||||
}
|
}
|
||||||
.ant-slider-track{
|
.ant-slider-track{
|
||||||
background-color:@App-primary-color !important;
|
background-color:@App-primary-color !important;
|
||||||
|
|||||||
@@ -61,8 +61,6 @@ interface Prop {
|
|||||||
placeHolder?: String
|
placeHolder?: String
|
||||||
}
|
}
|
||||||
|
|
||||||
const addonBgColor = computed(() => props.addonBgColor)
|
|
||||||
const addonTextColor = computed(() => props.addonTextColor)
|
|
||||||
//emits
|
//emits
|
||||||
const localValue = computed({
|
const localValue = computed({
|
||||||
set(value) {
|
set(value) {
|
||||||
@@ -132,8 +130,8 @@ const {prefixCls} = usePrefix('input')
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-input-group-addon {
|
.ant-input-group-addon {
|
||||||
background-color: v-bind(addonBgColor);
|
background-color: #e0be7f !important;
|
||||||
color: v-bind(addonTextColor);
|
color: #0a1b37 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -56,6 +56,11 @@ const router = createRouter({
|
|||||||
name: 'wallet',
|
name: 'wallet',
|
||||||
component: () => import('../views/base/wallet/index.vue')
|
component: () => import('../views/base/wallet/index.vue')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'assets',
|
||||||
|
name: 'assets',
|
||||||
|
component: () => import('../views/base/assets/index.vue')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'user',
|
path: 'user',
|
||||||
name: 'user',
|
name: 'user',
|
||||||
@@ -128,6 +133,11 @@ const router = createRouter({
|
|||||||
name: 'giftCard',
|
name: 'giftCard',
|
||||||
component: () => import('@/views/base/giftCard/index.vue')
|
component: () => import('@/views/base/giftCard/index.vue')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'convert',
|
||||||
|
name: 'convert',
|
||||||
|
component: () => import('@/views/base/convert/index.vue')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'installment',
|
path: 'installment',
|
||||||
name: 'installment',
|
name: 'installment',
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
export type ChartSeriesPoint = {
|
||||||
|
timeMs: number
|
||||||
|
value: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const CHART_PAD_Y = 14
|
||||||
|
const DEFAULT_MAX_POINTS = 22
|
||||||
|
|
||||||
|
function downsampleSeries(points: ChartSeriesPoint[], maxPoints: number): ChartSeriesPoint[] {
|
||||||
|
if (points.length <= maxPoints) return points
|
||||||
|
const result: ChartSeriesPoint[] = []
|
||||||
|
const lastIndex = points.length - 1
|
||||||
|
for (let i = 0; i < maxPoints; i += 1) {
|
||||||
|
const index = Math.round((i / (maxPoints - 1)) * lastIndex)
|
||||||
|
result.push(points[index])
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function smoothSeries(points: ChartSeriesPoint[], window = 3): ChartSeriesPoint[] {
|
||||||
|
if (points.length < window) return points
|
||||||
|
const half = Math.floor(window / 2)
|
||||||
|
return points.map((point, index) => {
|
||||||
|
let sum = 0
|
||||||
|
let count = 0
|
||||||
|
for (let i = index - half; i <= index + half; i += 1) {
|
||||||
|
if (i < 0 || i >= points.length) continue
|
||||||
|
sum += points[i].value
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
timeMs: point.timeMs,
|
||||||
|
value: count ? sum / count : point.value,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSmoothLinePath(plot: Array<{ x: number; y: number }>): string {
|
||||||
|
if (plot.length < 2) return ''
|
||||||
|
if (plot.length === 2) {
|
||||||
|
return `M${plot[0].x.toFixed(2)} ${plot[0].y.toFixed(2)} L${plot[1].x.toFixed(2)} ${plot[1].y.toFixed(2)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = `M${plot[0].x.toFixed(2)} ${plot[0].y.toFixed(2)}`
|
||||||
|
for (let i = 0; i < plot.length - 1; i += 1) {
|
||||||
|
const p0 = plot[i - 1] ?? plot[i]
|
||||||
|
const p1 = plot[i]
|
||||||
|
const p2 = plot[i + 1]
|
||||||
|
const p3 = plot[i + 2] ?? p2
|
||||||
|
const cp1x = p1.x + (p2.x - p0.x) / 6
|
||||||
|
const cp1y = p1.y + (p2.y - p0.y) / 6
|
||||||
|
const cp2x = p2.x - (p3.x - p1.x) / 6
|
||||||
|
const cp2y = p2.y - (p3.y - p1.y) / 6
|
||||||
|
path += ` C${cp1x.toFixed(2)} ${cp1y.toFixed(2)} ${cp2x.toFixed(2)} ${cp2y.toFixed(2)} ${p2.x.toFixed(2)} ${p2.y.toFixed(2)}`
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildSeriesChartPaths(
|
||||||
|
points: ChartSeriesPoint[],
|
||||||
|
width = 560,
|
||||||
|
height = 160,
|
||||||
|
options?: { maxPoints?: number; smooth?: boolean },
|
||||||
|
) {
|
||||||
|
const empty = {
|
||||||
|
hasData: false,
|
||||||
|
linePath: '',
|
||||||
|
areaPath: '',
|
||||||
|
endDot: null as { x: number; y: number } | null,
|
||||||
|
}
|
||||||
|
if (points.length < 2) return empty
|
||||||
|
|
||||||
|
const maxPoints = options?.maxPoints ?? DEFAULT_MAX_POINTS
|
||||||
|
const useSmooth = options?.smooth ?? true
|
||||||
|
|
||||||
|
let series = downsampleSeries(points, maxPoints)
|
||||||
|
if (useSmooth) {
|
||||||
|
series = smoothSeries(series, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = series.map((point) => point.value)
|
||||||
|
let min = Math.min(...values)
|
||||||
|
let max = Math.max(...values)
|
||||||
|
const span = max - min || max * 0.02 || 1
|
||||||
|
const pad = span * 0.12
|
||||||
|
min -= pad
|
||||||
|
max += pad
|
||||||
|
const range = max - min
|
||||||
|
const usableHeight = height - CHART_PAD_Y * 2
|
||||||
|
|
||||||
|
const plot = series.map((point, index) => {
|
||||||
|
const x = (index / (series.length - 1)) * width
|
||||||
|
const y = CHART_PAD_Y + (1 - (point.value - min) / range) * usableHeight
|
||||||
|
return { x, y }
|
||||||
|
})
|
||||||
|
|
||||||
|
const linePath = buildSmoothLinePath(plot)
|
||||||
|
const areaPath = `${linePath} L${width} ${height} L0 ${height} Z`
|
||||||
|
const last = plot[plot.length - 1]
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasData: true,
|
||||||
|
linePath,
|
||||||
|
areaPath,
|
||||||
|
endDot: last ? { x: last.x, y: last.y } : null,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { computed, ref, watch, type ComputedRef } from 'vue'
|
||||||
|
import {
|
||||||
|
fetchZaanboxPriceHistory,
|
||||||
|
type LandingHistoryRange,
|
||||||
|
} from '@/api/zaarboxPriceHistory'
|
||||||
|
import { buildSeriesChartPaths } from '@/utils/chartAreaPaths'
|
||||||
|
const CHART_RANGE_OPTIONS = [
|
||||||
|
{ value: 'DAILY' as const, shortLabel: '۲۴ ساعت', label: 'ارزش دارایی — ۲۴ ساعت گذشته' },
|
||||||
|
{ value: 'WEEKLY' as const, shortLabel: 'هفته', label: 'ارزش دارایی — هفته گذشته' },
|
||||||
|
{ value: 'MONTHLY' as const, shortLabel: 'ماه', label: 'ارزش دارایی — ماه گذشته' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export function useAssetsPortfolioChart(
|
||||||
|
cashRial: ComputedRef<number>,
|
||||||
|
metalRial: ComputedRef<number>,
|
||||||
|
) {
|
||||||
|
const chartRange = ref<LandingHistoryRange>('MONTHLY')
|
||||||
|
const chartDrawn = ref(false)
|
||||||
|
const chartLoading = ref(false)
|
||||||
|
const priceHistory = ref<Array<{ timeMs: number; price: number }>>([])
|
||||||
|
|
||||||
|
async function loadChartHistory(range: LandingHistoryRange) {
|
||||||
|
chartLoading.value = true
|
||||||
|
try {
|
||||||
|
priceHistory.value = await fetchZaanboxPriceHistory(range)
|
||||||
|
} catch {
|
||||||
|
priceHistory.value = []
|
||||||
|
} finally {
|
||||||
|
chartLoading.value = false
|
||||||
|
chartDrawn.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setChartRange(range: LandingHistoryRange) {
|
||||||
|
chartRange.value = range
|
||||||
|
void loadChartHistory(range)
|
||||||
|
}
|
||||||
|
|
||||||
|
const portfolioSeries = computed(() => {
|
||||||
|
const cash = Math.max(0, cashRial.value)
|
||||||
|
const metal = Math.max(0, metalRial.value)
|
||||||
|
const prices = priceHistory.value
|
||||||
|
|
||||||
|
if (prices.length < 2) {
|
||||||
|
const total = cash + metal
|
||||||
|
if (total <= 0) return []
|
||||||
|
return prices.map((point) => ({ timeMs: point.timeMs, value: total }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const refPrice = prices[prices.length - 1]?.price
|
||||||
|
if (!refPrice || metal <= 0) {
|
||||||
|
const total = cash + metal
|
||||||
|
return prices.map((point) => ({ timeMs: point.timeMs, value: total }))
|
||||||
|
}
|
||||||
|
|
||||||
|
return prices.map((point) => ({
|
||||||
|
timeMs: point.timeMs,
|
||||||
|
value: Math.round(cash + metal * (point.price / refPrice)),
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
const portfolioChart = computed(() => buildSeriesChartPaths(portfolioSeries.value))
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => cashRial.value + metalRial.value,
|
||||||
|
(total) => {
|
||||||
|
if (total > 0 && priceHistory.value.length === 0) {
|
||||||
|
void loadChartHistory(chartRange.value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ flush: 'post' },
|
||||||
|
)
|
||||||
|
|
||||||
|
async function initAssetsPortfolioChart() {
|
||||||
|
await loadChartHistory(chartRange.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
chartRange,
|
||||||
|
chartRangeOptions: CHART_RANGE_OPTIONS,
|
||||||
|
chartDrawn,
|
||||||
|
chartLoading,
|
||||||
|
portfolioChart,
|
||||||
|
setChartRange,
|
||||||
|
initAssetsPortfolioChart,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,485 @@
|
|||||||
|
<template>
|
||||||
|
<div class="assets-page" :class="{ 'assets-page--desktop': isDesktop }">
|
||||||
|
<MobilePageHeader v-if="!isDesktop" title="داراییها" show-back />
|
||||||
|
|
||||||
|
<div class="assets-page__shell">
|
||||||
|
<DesktopPageTopbar
|
||||||
|
v-if="isDesktop"
|
||||||
|
eyebrow="دارایی"
|
||||||
|
title="داراییها"
|
||||||
|
subtitle="خلاصه و جزئیات دارایی"
|
||||||
|
show-back
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="assets-page__body" :class="{ 'mpage-content': !isDesktop }">
|
||||||
|
<section class="assets-top">
|
||||||
|
<article class="assets-card assets-chart">
|
||||||
|
<div
|
||||||
|
class="assets-chart__plot"
|
||||||
|
:class="{ 'is-drawn': chartDrawn }"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
v-if="portfolioChart.hasData"
|
||||||
|
class="assets-chart__svg"
|
||||||
|
viewBox="0 0 560 160"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="assetsLiveFill" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0%" stop-color="#c98a5f" stop-opacity="0.3" />
|
||||||
|
<stop offset="100%" stop-color="#c98a5f" stop-opacity="0" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path class="assets-chart__area" :d="portfolioChart.areaPath" fill="url(#assetsLiveFill)" />
|
||||||
|
<path
|
||||||
|
class="assets-chart__line"
|
||||||
|
:d="portfolioChart.linePath"
|
||||||
|
pathLength="1"
|
||||||
|
fill="none"
|
||||||
|
stroke="#c98a5f"
|
||||||
|
stroke-width="3"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
<g v-if="portfolioChart.endDot" class="assets-chart__endpoint">
|
||||||
|
<circle :cx="portfolioChart.endDot.x" :cy="portfolioChart.endDot.y" r="5" fill="#c98a5f" />
|
||||||
|
<circle
|
||||||
|
:cx="portfolioChart.endDot.x"
|
||||||
|
:cy="portfolioChart.endDot.y"
|
||||||
|
r="10"
|
||||||
|
fill="#c98a5f"
|
||||||
|
fill-opacity="0.2"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<p v-else class="assets-chart__empty">نمودار ارزش دارایی در دسترس نیست</p>
|
||||||
|
</div>
|
||||||
|
<div class="assets-chart__ranges">
|
||||||
|
<button
|
||||||
|
v-for="option in chartRangeOptions"
|
||||||
|
:key="option.value"
|
||||||
|
type="button"
|
||||||
|
:class="{ 'is-active': chartRange === option.value }"
|
||||||
|
@click="setChartRange(option.value)"
|
||||||
|
>
|
||||||
|
{{ option.shortLabel }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="assets-card assets-balance">
|
||||||
|
<span class="assets-balance__label">ارزش کل دارایی</span>
|
||||||
|
<strong class="assets-balance__value">{{ usePrice(totalRialValue) }}</strong>
|
||||||
|
<span class="assets-balance__profit" :class="profitClass">{{ profitSheetLabel }}</span>
|
||||||
|
<span class="assets-balance__hint">سود / زیان تحققنیافته</span>
|
||||||
|
|
||||||
|
<div class="assets-facts">
|
||||||
|
<div v-for="item in summaryItems" :key="item.label" class="assets-facts__row">
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
<strong>{{ item.value }}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<nav class="assets-actions" aria-label="عملیات دارایی">
|
||||||
|
<button type="button" class="is-main" @click="go('wallet')">واریز</button>
|
||||||
|
<button type="button" @click="go('wallet')">برداشت</button>
|
||||||
|
<button type="button" @click="go('buyProduct')">معامله</button>
|
||||||
|
<button type="button" @click="go('transactions')">تراکنشها</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<section class="assets-card assets-table-wrap">
|
||||||
|
<div class="assets-table-wrap__head">
|
||||||
|
<h2>داراییها</h2>
|
||||||
|
<p>موجودی هر دارایی و ارزش ریالی آن</p>
|
||||||
|
</div>
|
||||||
|
<div class="assets-table">
|
||||||
|
<div class="assets-table__row assets-table__row--head">
|
||||||
|
<span>دارایی</span>
|
||||||
|
<span>موجودی</span>
|
||||||
|
<span>ارزش ریالی</span>
|
||||||
|
<span>تغییرات</span>
|
||||||
|
</div>
|
||||||
|
<div v-for="row in assetRows" :key="row.key" class="assets-table__row">
|
||||||
|
<span class="assets-table__name">{{ row.name }}</span>
|
||||||
|
<span>{{ row.balance }}</span>
|
||||||
|
<span>{{ row.rial }}</span>
|
||||||
|
<span :class="row.changeClass">{{ row.change }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="!assetRows.length" class="assets-table__empty">داراییای برای نمایش نیست.</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted } from 'vue'
|
||||||
|
import router from '@/router'
|
||||||
|
import MobilePageHeader from '@/layouts/components/MobilePageHeader.vue'
|
||||||
|
import DesktopPageTopbar from '@/layouts/components/DesktopPageTopbar.vue'
|
||||||
|
import { useDesktopBreakpoint } from '@/layouts/composables/useResponsiveLayout'
|
||||||
|
import { useWalletAssets } from '@/composables/useWalletAssets'
|
||||||
|
import { usePrice } from '@/utils/usePrice'
|
||||||
|
import { getTradeBuyPrice, lastPrice } from '@/utils/price'
|
||||||
|
import {
|
||||||
|
assetSheetInventoryCards,
|
||||||
|
formatInventoryCardValue,
|
||||||
|
formatPortfolioProfitPercent,
|
||||||
|
formatPortfolioProfitText,
|
||||||
|
resolveAssetsRialValue,
|
||||||
|
resolveMetalAssetsDisplayLabel,
|
||||||
|
} from '@/utils/productAssets'
|
||||||
|
import type { WalletMetalTotal } from '@/models/walletAssets'
|
||||||
|
import { useAssetsPortfolioChart } from './composables/useAssetsPortfolioChart'
|
||||||
|
|
||||||
|
const isDesktop = useDesktopBreakpoint()
|
||||||
|
const { assetsSummary, loadWalletAssets } = useWalletAssets()
|
||||||
|
|
||||||
|
const inventoryCards = computed(() => assetsSummary.value?.inventory_cards ?? [])
|
||||||
|
const metalTotals = computed(() => assetsSummary.value?.metal_totals ?? [])
|
||||||
|
const cashWallet = computed(() => assetsSummary.value?.cash_wallet ?? 0)
|
||||||
|
const metalRialValue = computed(() => {
|
||||||
|
const fromSummary = Number(assetsSummary.value?.assets_rial_value ?? 0)
|
||||||
|
if (fromSummary > 0) return fromSummary
|
||||||
|
return resolveAssetsRialValue(assetsSummary.value, getTradeBuyPrice(lastPrice.value))
|
||||||
|
})
|
||||||
|
const totalRialValue = computed(() => {
|
||||||
|
const fromSummary = Number(assetsSummary.value?.total_rial_value ?? 0)
|
||||||
|
if (fromSummary > 0) return fromSummary
|
||||||
|
return cashWallet.value + metalRialValue.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
chartRange,
|
||||||
|
chartRangeOptions,
|
||||||
|
chartDrawn,
|
||||||
|
portfolioChart,
|
||||||
|
setChartRange,
|
||||||
|
initAssetsPortfolioChart,
|
||||||
|
} = useAssetsPortfolioChart(cashWallet, metalRialValue)
|
||||||
|
const portfolioInsight = computed(() => assetsSummary.value?.portfolio)
|
||||||
|
|
||||||
|
const profitSheetLabel = computed(() => {
|
||||||
|
const profit = portfolioInsight.value?.unrealized_profit_rial
|
||||||
|
if (profit == null) return '—'
|
||||||
|
const pct = formatPortfolioProfitPercent(portfolioInsight.value?.unrealized_profit_percent)
|
||||||
|
return `${formatPortfolioProfitText(profit)}${pct ? ` (${pct})` : ''}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const profitClass = computed(() => {
|
||||||
|
const profit = portfolioInsight.value?.unrealized_profit_rial
|
||||||
|
if (profit == null || profit === 0) return ''
|
||||||
|
return profit > 0 ? 'is-profit' : 'is-loss'
|
||||||
|
})
|
||||||
|
|
||||||
|
const summaryItems = computed(() => {
|
||||||
|
const items = metalTotals.value.map((metal) => ({
|
||||||
|
label: assetsMetalLabel(metal),
|
||||||
|
value: `${metal.total_weight} ${metal.unit}`,
|
||||||
|
}))
|
||||||
|
items.push(
|
||||||
|
{ label: 'کیف پول ریالی', value: usePrice(cashWallet.value) },
|
||||||
|
{ label: 'ارزش ریالی مس', value: usePrice(metalRialValue.value) },
|
||||||
|
)
|
||||||
|
if (portfolioInsight.value?.average_buy_price != null) {
|
||||||
|
items.push({
|
||||||
|
label: 'میانگین خرید',
|
||||||
|
value: usePrice(portfolioInsight.value.average_buy_price ?? 0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
})
|
||||||
|
|
||||||
|
const assetRows = computed(() => {
|
||||||
|
const pct = formatPortfolioProfitPercent(portfolioInsight.value?.unrealized_profit_percent)
|
||||||
|
const profit = portfolioInsight.value?.unrealized_profit_rial
|
||||||
|
const changeClass = profit == null || profit === 0 ? '' : profit > 0 ? 'is-profit' : 'is-loss'
|
||||||
|
const rows = metalTotals.value.flatMap((metal) => {
|
||||||
|
const head = {
|
||||||
|
key: `metal-${metal.metal_id}`,
|
||||||
|
name: assetsMetalLabel(metal),
|
||||||
|
balance: `${metal.total_weight} ${metal.unit}`,
|
||||||
|
rial: usePrice(metalRialValue.value),
|
||||||
|
change: pct || '—',
|
||||||
|
changeClass,
|
||||||
|
}
|
||||||
|
const cards = assetSheetInventoryCards(inventoryCards.value, metal.metal_id).map((card) => ({
|
||||||
|
key: `card-${card.product_id}`,
|
||||||
|
name: assetsDisplayText(card.title),
|
||||||
|
balance: formatInventoryCardValue(card),
|
||||||
|
rial: '—',
|
||||||
|
change: '—',
|
||||||
|
changeClass: '',
|
||||||
|
}))
|
||||||
|
return [head, ...cards]
|
||||||
|
})
|
||||||
|
rows.push({
|
||||||
|
key: 'cash',
|
||||||
|
name: 'کیف پول ریالی',
|
||||||
|
balance: usePrice(cashWallet.value),
|
||||||
|
rial: usePrice(cashWallet.value),
|
||||||
|
change: '—',
|
||||||
|
changeClass: '',
|
||||||
|
})
|
||||||
|
return rows
|
||||||
|
})
|
||||||
|
|
||||||
|
function assetsDisplayText(text: string | null | undefined): string {
|
||||||
|
return String(text ?? '').replace(/طلا/g, 'مس')
|
||||||
|
}
|
||||||
|
|
||||||
|
function assetsMetalLabel(metal: WalletMetalTotal): string {
|
||||||
|
return assetsDisplayText(resolveMetalAssetsDisplayLabel(metal))
|
||||||
|
}
|
||||||
|
|
||||||
|
function go(name: string) {
|
||||||
|
router.push({ name })
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadWalletAssets(true)
|
||||||
|
await initAssetsPortfolioChart()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.assets-page {
|
||||||
|
--assets-line: @App-primary-color;
|
||||||
|
--assets-line-soft: color-mix(in srgb, @App-primary-color 65%, #c9a062);
|
||||||
|
min-height: 100%;
|
||||||
|
padding-bottom: 1.5rem;
|
||||||
|
color: inherit;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-page--desktop {
|
||||||
|
background: #f6f7f9;
|
||||||
|
color: #111827;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-page--desktop .assets-page__shell {
|
||||||
|
max-width: 1180px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 28px 36px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-page__body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
max-width: 1100px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-page:not(.assets-page--desktop) .assets-page__body {
|
||||||
|
padding-top: var(--mpage-header-offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-page--desktop .assets-page__body {
|
||||||
|
max-width: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-top {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.assets-top {
|
||||||
|
grid-template-columns: 1.15fr 0.85fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-card {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid color-mix(in srgb, @App-primary-color 14%, #fff);
|
||||||
|
border-radius: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-chart {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.35rem;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 0.85rem 0.75rem 0.75rem;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-chart__plot {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 168px;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-chart__svg {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-chart__line {
|
||||||
|
stroke-dasharray: 1;
|
||||||
|
stroke-dashoffset: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-chart__area,
|
||||||
|
.assets-chart__endpoint {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-chart__plot.is-drawn .assets-chart__line {
|
||||||
|
animation: assets-chart-draw 1.55s cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-chart__plot.is-drawn .assets-chart__area {
|
||||||
|
animation: assets-chart-fade 0.55s ease-out 0.85s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-chart__plot.is-drawn .assets-chart__endpoint {
|
||||||
|
animation: assets-chart-fade 0.35s ease-out 1.35s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes assets-chart-draw {
|
||||||
|
to {
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes assets-chart-fade {
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.assets-chart__empty {
|
||||||
|
margin: 3rem 0;
|
||||||
|
text-align: center;
|
||||||
|
opacity: 0.55;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-chart__ranges {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-chart__ranges button,
|
||||||
|
.assets-actions button {
|
||||||
|
border: 1px solid color-mix(in srgb, @App-primary-color 16%, #fff);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: color-mix(in srgb, @App-primary-color 6%, #fff);
|
||||||
|
color: inherit;
|
||||||
|
padding: 0.45rem 0.85rem;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-chart__ranges button.is-active,
|
||||||
|
.assets-actions button.is-main {
|
||||||
|
background: @App-primary-color;
|
||||||
|
border-color: @App-primary-color;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-balance {
|
||||||
|
padding: 1rem 1rem 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-balance__label,
|
||||||
|
.assets-balance__hint,
|
||||||
|
.assets-facts__row span,
|
||||||
|
.assets-table-wrap__head p {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
opacity: 0.62;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-balance__value {
|
||||||
|
display: block;
|
||||||
|
margin: 0.25rem 0;
|
||||||
|
font-size: 1.7rem;
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-balance__profit {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-balance__profit.is-profit,
|
||||||
|
.assets-table__row .is-profit {
|
||||||
|
color: #1f8a4c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-balance__profit.is-loss,
|
||||||
|
.assets-table__row .is-loss {
|
||||||
|
color: #ff6b6b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-facts {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.35rem;
|
||||||
|
margin-top: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-facts__row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding-top: 0.35rem;
|
||||||
|
border-top: 1px solid color-mix(in srgb, @App-primary-color 12%, #fff);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-actions {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: 0.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-table-wrap {
|
||||||
|
padding: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-table-wrap__head h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-table-wrap__head p {
|
||||||
|
margin: 0.15rem 0 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-table__row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.3fr 1fr 1.2fr 0.7fr;
|
||||||
|
gap: 0.35rem;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.6rem 0;
|
||||||
|
border-top: 1px solid color-mix(in srgb, @App-primary-color 12%, #fff);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-table__row--head {
|
||||||
|
border-top: 0;
|
||||||
|
opacity: 0.55;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-table__name {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-table__empty {
|
||||||
|
padding: 1rem 0;
|
||||||
|
text-align: center;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -164,8 +164,9 @@ function formatRings(value: number) {
|
|||||||
.copper-convert {
|
.copper-convert {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.5rem;
|
gap: 0.75rem;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
color: #334155;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copper-convert__lead {
|
.copper-convert__lead {
|
||||||
@@ -175,28 +176,13 @@ function formatRings(value: number) {
|
|||||||
color: #64748b;
|
color: #64748b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copper-convert__panel {
|
.copper-convert__panel,
|
||||||
position: relative;
|
.copper-convert__form,
|
||||||
padding: 0.75rem 0.875rem 0.7rem;
|
.copper-convert__result {
|
||||||
border-radius: 1rem;
|
padding: 0.875rem;
|
||||||
background: linear-gradient(145deg, #0f2744 0%, #152f4f 48%, #1a365d 100%);
|
border-radius: 0.875rem;
|
||||||
border: 1px solid rgba(201, 160, 98, 0.35);
|
background: #fff;
|
||||||
box-shadow:
|
border: 1px solid #e5e7eb;
|
||||||
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 {
|
.copper-convert__panel-head {
|
||||||
@@ -208,17 +194,18 @@ function formatRings(value: number) {
|
|||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copper-convert__panel-kicker {
|
.copper-convert__panel-kicker,
|
||||||
|
.copper-convert__result-kicker {
|
||||||
font-size: 0.6875rem;
|
font-size: 0.6875rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0.04em;
|
color: var(--colorPrimary, #2563eb);
|
||||||
color: rgba(201, 160, 98, 0.95);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.copper-convert__panel-title {
|
.copper-convert__panel-title,
|
||||||
|
.copper-convert__result-title {
|
||||||
font-size: 0.9375rem;
|
font-size: 0.9375rem;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
color: #f8fafc;
|
color: #0f172a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copper-convert__price-group {
|
.copper-convert__price-group {
|
||||||
@@ -230,7 +217,7 @@ function formatRings(value: number) {
|
|||||||
.copper-convert__price-group--wire {
|
.copper-convert__price-group--wire {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
padding-top: 0.65rem;
|
padding-top: 0.65rem;
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
border-top: 1px solid #f1f5f9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copper-convert__group-label {
|
.copper-convert__group-label {
|
||||||
@@ -238,7 +225,7 @@ function formatRings(value: number) {
|
|||||||
margin-bottom: 0.45rem;
|
margin-bottom: 0.45rem;
|
||||||
font-size: 0.6875rem;
|
font-size: 0.6875rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: rgba(226, 232, 240, 0.75);
|
color: #64748b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copper-convert__price-grid {
|
.copper-convert__price-grid {
|
||||||
@@ -258,29 +245,29 @@ function formatRings(value: number) {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding: 0.45rem 0.5rem;
|
padding: 0.45rem 0.5rem;
|
||||||
border-radius: 0.65rem;
|
border-radius: 0.65rem;
|
||||||
background: rgba(255, 255, 255, 0.06);
|
background: #f8fafc;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
border: 1px solid #e5e7eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copper-convert__chip--accent {
|
.copper-convert__chip--accent {
|
||||||
background: rgba(201, 160, 98, 0.12);
|
background: #eff5f3;
|
||||||
border-color: rgba(201, 160, 98, 0.35);
|
border-color: #d4e3de;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copper-convert__chip--muted .copper-convert__chip-value {
|
.copper-convert__chip--muted .copper-convert__chip-value {
|
||||||
color: rgba(248, 250, 252, 0.45);
|
color: #94a3b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copper-convert__chip-label {
|
.copper-convert__chip-label {
|
||||||
font-size: 0.625rem;
|
font-size: 0.625rem;
|
||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
color: rgba(226, 232, 240, 0.85);
|
color: #64748b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copper-convert__chip-value {
|
.copper-convert__chip-value {
|
||||||
font-size: 0.6875rem;
|
font-size: 0.6875rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #f8fafc;
|
color: #0f172a;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@@ -290,11 +277,6 @@ function formatRings(value: number) {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.65rem;
|
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 {
|
.copper-convert__field-label {
|
||||||
@@ -327,9 +309,9 @@ function formatRings(value: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.copper-convert__pill--active {
|
.copper-convert__pill--active {
|
||||||
color: #0f2744;
|
color: var(--colorPrimary, #2563eb);
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: 0 2px 8px rgba(15, 39, 68, 0.12);
|
box-shadow: 0 1px 4px rgba(15, 23, 42, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.copper-convert__weight-wrap {
|
.copper-convert__weight-wrap {
|
||||||
@@ -352,7 +334,7 @@ function formatRings(value: number) {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
font-size: 1.125rem;
|
font-size: 1.125rem;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
color: #0f2744;
|
color: #0f172a;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
}
|
}
|
||||||
@@ -367,16 +349,13 @@ function formatRings(value: number) {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
color: #c9a062;
|
color: var(--colorPrimary, #2563eb);
|
||||||
}
|
}
|
||||||
|
|
||||||
.copper-convert__result {
|
.copper-convert__result {
|
||||||
margin-top: 0.125rem;
|
display: flex;
|
||||||
padding: 0.875rem;
|
flex-direction: column;
|
||||||
border-radius: 1rem;
|
gap: 0.75rem;
|
||||||
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 {
|
.copper-convert__result-head {
|
||||||
@@ -386,17 +365,6 @@ function formatRings(value: number) {
|
|||||||
margin-bottom: 0.75rem;
|
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 {
|
.copper-convert__rings {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -410,23 +378,11 @@ function formatRings(value: number) {
|
|||||||
padding: 0.75rem 0.5rem;
|
padding: 0.75rem 0.5rem;
|
||||||
border-radius: 0.85rem;
|
border-radius: 0.85rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background: linear-gradient(160deg, #0f2744 0%, #1a365d 100%);
|
background: #eff5f3;
|
||||||
border: 1px solid rgba(201, 160, 98, 0.4);
|
border: 1px solid #d4e3de;
|
||||||
overflow: hidden;
|
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 {
|
.copper-convert__ring-badge {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-bottom: 0.35rem;
|
margin-bottom: 0.35rem;
|
||||||
@@ -434,8 +390,8 @@ function formatRings(value: number) {
|
|||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
font-size: 0.625rem;
|
font-size: 0.625rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #0f2744;
|
color: var(--colorPrimary, #2563eb);
|
||||||
background: rgba(201, 160, 98, 0.95);
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copper-convert__ring-count {
|
.copper-convert__ring-count {
|
||||||
@@ -443,20 +399,20 @@ function formatRings(value: number) {
|
|||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
color: #f8fafc;
|
color: #0f172a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copper-convert__ring-caption {
|
.copper-convert__ring-caption {
|
||||||
margin: 0.15rem 0 0;
|
margin: 0.15rem 0 0;
|
||||||
font-size: 0.6875rem;
|
font-size: 0.6875rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: rgba(226, 232, 240, 0.85);
|
color: #64748b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copper-convert__ring-sub {
|
.copper-convert__ring-sub {
|
||||||
margin: 0.35rem 0 0;
|
margin: 0.35rem 0 0;
|
||||||
font-size: 0.625rem;
|
font-size: 0.625rem;
|
||||||
color: rgba(201, 160, 98, 0.9);
|
color: #64748b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copper-convert__summary {
|
.copper-convert__summary {
|
||||||
@@ -479,7 +435,7 @@ function formatRings(value: number) {
|
|||||||
.copper-convert__summary-row strong {
|
.copper-convert__summary-row strong {
|
||||||
font-size: 0.8125rem;
|
font-size: 0.8125rem;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
color: #0f2744;
|
color: #0f172a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copper-convert__summary-row strong small {
|
.copper-convert__summary-row strong small {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
computed,
|
computed,
|
||||||
inject,
|
inject,
|
||||||
|
onMounted,
|
||||||
|
onUnmounted,
|
||||||
provide,
|
provide,
|
||||||
ref,
|
ref,
|
||||||
type ComputedRef,
|
type ComputedRef,
|
||||||
@@ -144,14 +146,14 @@ export function useCopperConvertPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshLivePrices() {
|
onMounted(() => {
|
||||||
startLastPricePolling()
|
startLastPricePolling()
|
||||||
await loadCatalog()
|
void loadCatalog()
|
||||||
}
|
})
|
||||||
|
|
||||||
function stopLivePrices() {
|
onUnmounted(() => {
|
||||||
stopLastPricePolling()
|
stopLastPricePolling()
|
||||||
}
|
})
|
||||||
|
|
||||||
const ctx: CopperConvertPageContext = {
|
const ctx: CopperConvertPageContext = {
|
||||||
loading,
|
loading,
|
||||||
@@ -163,7 +165,7 @@ export function useCopperConvertPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
provide(COPPER_CONVERT_PAGE_KEY, ctx)
|
provide(COPPER_CONVERT_PAGE_KEY, ctx)
|
||||||
return { ...ctx, refreshLivePrices, stopLivePrices, loadCatalog }
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCopperConvertPageContext() {
|
export function useCopperConvertPageContext() {
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
<template>
|
||||||
|
<div class="dconvert">
|
||||||
|
<div class="dconvert__container">
|
||||||
|
<header class="dconvert-topbar">
|
||||||
|
<button type="button" class="dconvert-topbar__back" @click="router.back()">
|
||||||
|
<BasicIcon icon="vuesax-linear:arrow-right-1" size="18" />
|
||||||
|
<span>بازگشت</span>
|
||||||
|
</button>
|
||||||
|
<div class="dconvert-topbar__meta">
|
||||||
|
<p class="dconvert-topbar__eyebrow">محصول</p>
|
||||||
|
<h1 class="dconvert-topbar__title">تبدیل محصول</h1>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="dconvert-surface">
|
||||||
|
<CopperConvertCalculator />
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||||
|
import router from '@/router'
|
||||||
|
import CopperConvertCalculator from '../components/CopperConvertCalculator.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.dconvert {
|
||||||
|
min-height: 100%;
|
||||||
|
padding: 1.25rem 1.5rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dconvert__container {
|
||||||
|
max-width: 720px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dconvert-topbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dconvert-topbar__back {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--colorPrimary, #2563eb);
|
||||||
|
font-family: inherit;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dconvert-topbar__eyebrow {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dconvert-topbar__title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dconvert-surface {
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 0.875rem;
|
||||||
|
background: #fff;
|
||||||
|
padding: 1.25rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<template>
|
||||||
|
<div class="convert-page-root">
|
||||||
|
<DesktopConvertView v-show="isDesktop" />
|
||||||
|
<MobileConvertView v-show="!isDesktop" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
|
||||||
|
import { useCopperConvertPage } from './composables/useCopperConvertPage'
|
||||||
|
import DesktopConvertView from './desktop/DesktopConvertView.vue'
|
||||||
|
import MobileConvertView from './mobile/MobileConvertView.vue'
|
||||||
|
|
||||||
|
const isDesktop = useDesktopBreakpoint()
|
||||||
|
|
||||||
|
useCopperConvertPage()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.convert-page-root {
|
||||||
|
min-height: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<MobilePageHeader title="تبدیل محصول" show-back />
|
||||||
|
<div class="px-5 mpage-content pb-2">
|
||||||
|
<div class="border-gray-200 border-1 rounded-xl p-4 relative bg-white">
|
||||||
|
<CopperConvertCalculator />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import MobilePageHeader from '@/layouts/components/MobilePageHeader.vue'
|
||||||
|
import CopperConvertCalculator from '../components/CopperConvertCalculator.vue'
|
||||||
|
</script>
|
||||||
@@ -42,14 +42,14 @@
|
|||||||
<bg_main />
|
<bg_main />
|
||||||
<div class="z-100 relative">
|
<div class="z-100 relative">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<BasicInput v-model:value="form.goldCash" addon-before="ریال" :addon-bg-color="useConfigStore().config.color"
|
<BasicInput v-model:value="form.goldCash" addon-before="ریال"
|
||||||
addon-text-color="white"
|
addon-text-color="white"
|
||||||
place-holder="ارزش کل"
|
place-holder="ارزش کل"
|
||||||
size="large"
|
size="large"
|
||||||
type="price" @onchange="calculateGold" />
|
type="price" @onchange="calculateGold" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :rules="[justNumber()]">
|
<a-form-item :rules="[justNumber()]">
|
||||||
<BasicInput v-model:value="form.goldValue" addon-before="گرم" :addon-bg-color="useConfigStore().config.color"
|
<BasicInput v-model:value="form.goldValue" addon-before="گرم"
|
||||||
addon-text-color="white"
|
addon-text-color="white"
|
||||||
place-holder="مقدار طلا"
|
place-holder="مقدار طلا"
|
||||||
size="large" type="text" inputmode="decimal" @onchange="calculateCash" />
|
size="large" type="text" inputmode="decimal" @onchange="calculateCash" />
|
||||||
|
|||||||
@@ -74,14 +74,14 @@
|
|||||||
<bg_main />
|
<bg_main />
|
||||||
<div class="z-100 relative">
|
<div class="z-100 relative">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<BasicInput v-model:value="form.goldCash" addon-before="ریال" :addon-bg-color="useConfigStore().config.color"
|
<BasicInput v-model:value="form.goldCash" addon-before="ریال"
|
||||||
addon-text-color="white"
|
addon-text-color="white"
|
||||||
place-holder="ارزش کل"
|
place-holder="ارزش کل"
|
||||||
size="large"
|
size="large"
|
||||||
type="price" @onchange="calculateGold" />
|
type="price" @onchange="calculateGold" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :rules="[justNumber()]">
|
<a-form-item :rules="[justNumber()]">
|
||||||
<BasicInput v-model:value="form.goldValue" :addon-before="weightUnit" :addon-bg-color="useConfigStore().config.color"
|
<BasicInput v-model:value="form.goldValue" :addon-before="weightUnit"
|
||||||
addon-text-color="white"
|
addon-text-color="white"
|
||||||
:place-holder="amountFieldLabel"
|
:place-holder="amountFieldLabel"
|
||||||
size="large" type="text" inputmode="decimal" @onchange="calculateCash" />
|
size="large" type="text" inputmode="decimal" @onchange="calculateCash" />
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export function useHomePage() {
|
|||||||
icon: 'vuesax-linear:receipt-item',
|
icon: 'vuesax-linear:receipt-item',
|
||||||
label: 'موجودی',
|
label: 'موجودی',
|
||||||
handler: () => {
|
handler: () => {
|
||||||
wholeBalanceVisible.value = true
|
router.push({ name: 'assets' })
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -110,7 +110,7 @@ export function useHomePage() {
|
|||||||
icon: 'vuesax-linear:cup',
|
icon: 'vuesax-linear:cup',
|
||||||
label: 'دارایی ها',
|
label: 'دارایی ها',
|
||||||
handler: () => {
|
handler: () => {
|
||||||
wholeBalanceVisible.value = true
|
router.push({ name: 'assets' })
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -192,7 +192,7 @@ export function useHomePage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function openAssetsSheet() {
|
function openAssetsSheet() {
|
||||||
wholeBalanceVisible.value = true
|
router.push({ name: 'assets' })
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleBannerClick(banner: { typeClick?: string; modalText?: string; link?: string }) {
|
function handleBannerClick(banner: { typeClick?: string; modalText?: string; link?: string }) {
|
||||||
|
|||||||
@@ -4,12 +4,6 @@
|
|||||||
|
|
||||||
<LogoutConfirmDialog v-model:show="showLogoutConfirm" @confirm="logout" />
|
<LogoutConfirmDialog v-model:show="showLogoutConfirm" @confirm="logout" />
|
||||||
|
|
||||||
<BalanceSheet
|
|
||||||
v-model:show="wholeBalanceVisible"
|
|
||||||
:wallet-info="walletStore.walletInfo"
|
|
||||||
:gold-val="goldVal"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<BasicModal v-model:visible="showTextBanner" :custom-footer="true" destroy-on-close>
|
<BasicModal v-model:visible="showTextBanner" :custom-footer="true" destroy-on-close>
|
||||||
<div class="max-h-[54vh] ql-editor" v-html="TextBanner" />
|
<div class="max-h-[54vh] ql-editor" v-html="TextBanner" />
|
||||||
</BasicModal>
|
</BasicModal>
|
||||||
@@ -21,7 +15,6 @@
|
|||||||
<script lang="tsx" setup>
|
<script lang="tsx" setup>
|
||||||
import BasicModal from '@/components/BasicPacks/BasicModal.vue'
|
import BasicModal from '@/components/BasicPacks/BasicModal.vue'
|
||||||
import LogoutConfirmDialog from './components/LogoutConfirmDialog.vue'
|
import LogoutConfirmDialog from './components/LogoutConfirmDialog.vue'
|
||||||
import BalanceSheet from './components/BalanceSheet.vue'
|
|
||||||
import ShareModal from './components/ShareModal.vue'
|
import ShareModal from './components/ShareModal.vue'
|
||||||
import HomePageShell from './shell/HomePageShell.vue'
|
import HomePageShell from './shell/HomePageShell.vue'
|
||||||
import { useHomePage } from './composables/useHomePage'
|
import { useHomePage } from './composables/useHomePage'
|
||||||
@@ -30,13 +23,10 @@ import { recoverMobileViewportAfterAuth } from '@/layouts/composables/useMobileS
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
userStore,
|
userStore,
|
||||||
walletStore,
|
|
||||||
showLogoutConfirm,
|
showLogoutConfirm,
|
||||||
wholeBalanceVisible,
|
|
||||||
showTextBanner,
|
showTextBanner,
|
||||||
showShare,
|
showShare,
|
||||||
TextBanner,
|
TextBanner,
|
||||||
goldVal,
|
|
||||||
logout,
|
logout,
|
||||||
} = useHomePage()
|
} = useHomePage()
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
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'
|
||||||
|
|
||||||
@@ -22,7 +21,7 @@ export const boursecuQuickActions: HomeQuickActionItem[] = [
|
|||||||
label: 'دارایی ها',
|
label: 'دارایی ها',
|
||||||
icon: 'vuesax-linear:cup',
|
icon: 'vuesax-linear:cup',
|
||||||
iconSrc: getBoursecuActionIconSrc('assets'),
|
iconSrc: getBoursecuActionIconSrc('assets'),
|
||||||
// شیت موجودیها — نه مسیر fund (با enable_product_funds=false گارد بیصدا برمیگرداند)
|
route: { name: 'assets' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'transactions',
|
id: 'transactions',
|
||||||
@@ -43,6 +42,6 @@ export const boursecuQuickActions: HomeQuickActionItem[] = [
|
|||||||
label: 'تبدیل',
|
label: 'تبدیل',
|
||||||
icon: 'vuesax-linear:refresh-circle',
|
icon: 'vuesax-linear:refresh-circle',
|
||||||
iconSrc: getBoursecuActionIconSrc('convert'),
|
iconSrc: getBoursecuActionIconSrc('convert'),
|
||||||
handler: () => openCopperConvert(),
|
route: { name: 'convert' },
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -43,8 +43,6 @@
|
|||||||
<BasicInput
|
<BasicInput
|
||||||
v-model:value="item.count"
|
v-model:value="item.count"
|
||||||
:addon-before="quantityUnit"
|
:addon-before="quantityUnit"
|
||||||
:addon-bg-color="config.config?.color"
|
|
||||||
addon-text-color="white"
|
|
||||||
type="number"
|
type="number"
|
||||||
:inputmode="isCountBased ? 'numeric' : 'decimal'"
|
:inputmode="isCountBased ? 'numeric' : 'decimal'"
|
||||||
@update:value="onCountInput"
|
@update:value="onCountInput"
|
||||||
@@ -61,7 +59,6 @@
|
|||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||||
import BasicInput from '@/components/BasicPacks/BasicInput.vue'
|
import BasicInput from '@/components/BasicPacks/BasicInput.vue'
|
||||||
import { useConfigStore } from '@/store/configStore'
|
|
||||||
import type { CountPInventory } from '@/models/products'
|
import type { CountPInventory } from '@/models/products'
|
||||||
import { getProductDisplayImage } from '@/utils/productAssets'
|
import { getProductDisplayImage } from '@/utils/productAssets'
|
||||||
import {
|
import {
|
||||||
@@ -110,8 +107,6 @@ const displayMinDelivery = computed(() => {
|
|||||||
: formatGoldAmount(minDeliveryQuantity.value)
|
: formatGoldAmount(minDeliveryQuantity.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
const config = useConfigStore()
|
|
||||||
|
|
||||||
function selectCountInput(e: FocusEvent) {
|
function selectCountInput(e: FocusEvent) {
|
||||||
const el = e.target as HTMLInputElement | null
|
const el = e.target as HTMLInputElement | null
|
||||||
if (!el || el.tagName !== 'INPUT' || typeof el.select !== 'function') return
|
if (!el || el.tagName !== 'INPUT' || typeof el.select !== 'function') return
|
||||||
|
|||||||
@@ -243,8 +243,11 @@ export function useWalletPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (router.currentRoute.value.query.state === 'deposit') {
|
const walletState = router.currentRoute.value.query.state
|
||||||
|
if (walletState === 'deposit') {
|
||||||
isDeposit.value = true
|
isDeposit.value = true
|
||||||
|
} else if (walletState === 'withdraw') {
|
||||||
|
isDeposit.value = false
|
||||||
}
|
}
|
||||||
cardForm.value.birth_date = userStore.userInfo?.birth_date
|
cardForm.value.birth_date = userStore.userInfo?.birth_date
|
||||||
await initWalletPageData()
|
await initWalletPageData()
|
||||||
|
|||||||
@@ -45,8 +45,6 @@
|
|||||||
<BasicInput
|
<BasicInput
|
||||||
v-model:value="form.value"
|
v-model:value="form.value"
|
||||||
addon-before="ریال"
|
addon-before="ریال"
|
||||||
:addon-bg-color="useConfigStore().config.color"
|
|
||||||
addon-text-color="white"
|
|
||||||
place-holder="مبلغ"
|
place-holder="مبلغ"
|
||||||
size="large"
|
size="large"
|
||||||
type="price"
|
type="price"
|
||||||
@@ -175,7 +173,6 @@ import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
|||||||
import BasicInput from '@/components/BasicPacks/BasicInput.vue'
|
import BasicInput from '@/components/BasicPacks/BasicInput.vue'
|
||||||
import ListAnim from '@/components/ListAnim.vue'
|
import ListAnim from '@/components/ListAnim.vue'
|
||||||
import TransactionSkeleton from '@/components/TransactionSkeleton.vue'
|
import TransactionSkeleton from '@/components/TransactionSkeleton.vue'
|
||||||
import { useConfigStore } from '@/store/configStore'
|
|
||||||
import { required } from '@/utils/inputRules'
|
import { required } from '@/utils/inputRules'
|
||||||
import BoursecuBankAccountPicker from '../components/BoursecuBankAccountPicker.vue'
|
import BoursecuBankAccountPicker from '../components/BoursecuBankAccountPicker.vue'
|
||||||
import BoursecuWithdrawGuide from '../components/BoursecuWithdrawGuide.vue'
|
import BoursecuWithdrawGuide from '../components/BoursecuWithdrawGuide.vue'
|
||||||
|
|||||||
@@ -50,8 +50,6 @@
|
|||||||
<BasicInput
|
<BasicInput
|
||||||
v-model:value="form.value"
|
v-model:value="form.value"
|
||||||
addon-before="ریال"
|
addon-before="ریال"
|
||||||
:addon-bg-color="useConfigStore().config.color"
|
|
||||||
addon-text-color="white"
|
|
||||||
place-holder="مبلغ"
|
place-holder="مبلغ"
|
||||||
size="large"
|
size="large"
|
||||||
type="price"
|
type="price"
|
||||||
@@ -145,7 +143,6 @@ import BasicInput from '@/components/BasicPacks/BasicInput.vue'
|
|||||||
import Bg_main from '@/components/bg_main.vue'
|
import Bg_main from '@/components/bg_main.vue'
|
||||||
import ListAnim from '@/components/ListAnim.vue'
|
import ListAnim from '@/components/ListAnim.vue'
|
||||||
import TransactionSkeleton from '@/components/TransactionSkeleton.vue'
|
import TransactionSkeleton from '@/components/TransactionSkeleton.vue'
|
||||||
import { useConfigStore } from '@/store/configStore'
|
|
||||||
import { required } from '@/utils/inputRules'
|
import { required } from '@/utils/inputRules'
|
||||||
import img from '@/assets/img/user_ly1.svg'
|
import img from '@/assets/img/user_ly1.svg'
|
||||||
import BoursecuBankAccountPicker from '../components/BoursecuBankAccountPicker.vue'
|
import BoursecuBankAccountPicker from '../components/BoursecuBankAccountPicker.vue'
|
||||||
|
|||||||
Reference in New Issue
Block a user