ui changes
This commit is contained in:
@@ -426,6 +426,48 @@ export function formatPortfolioProfitPercent(percent?: number | null): string {
|
|||||||
return `${sign}${value.toFixed(2)}٪`
|
return `${sign}${value.toFixed(2)}٪`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** متن توضیح مودال سود/زیان تحققنیافته طلا */
|
||||||
|
export function formatPortfolioProfitInfoText(
|
||||||
|
portfolio: {
|
||||||
|
average_buy_price?: number | null
|
||||||
|
live_gold_price?: number | null
|
||||||
|
cost_basis_rial?: number | null
|
||||||
|
gold_weight?: number | null
|
||||||
|
gold_rial_value?: number | null
|
||||||
|
unrealized_profit_rial?: number | null
|
||||||
|
unrealized_profit_percent?: number | null
|
||||||
|
},
|
||||||
|
weightUnit = 'گرم'
|
||||||
|
): string {
|
||||||
|
const lines: string[] = []
|
||||||
|
|
||||||
|
if (portfolio.average_buy_price != null && portfolio.average_buy_price > 0) {
|
||||||
|
lines.push(`میانگین قیمت خرید: ${formatRialNumber(portfolio.average_buy_price)} ریال`)
|
||||||
|
}
|
||||||
|
if (portfolio.live_gold_price != null && portfolio.live_gold_price > 0) {
|
||||||
|
lines.push(`قیمت فعلی طلا: ${formatRialNumber(portfolio.live_gold_price)} ریال`)
|
||||||
|
}
|
||||||
|
if (portfolio.gold_weight != null && portfolio.gold_weight > 0) {
|
||||||
|
lines.push(`وزن طلای شما: ${formatWeightAmount(portfolio.gold_weight)} ${weightUnit}`)
|
||||||
|
}
|
||||||
|
if (portfolio.cost_basis_rial != null && portfolio.cost_basis_rial > 0) {
|
||||||
|
lines.push(`بهای تمامشده طلا: ${formatRialNumber(portfolio.cost_basis_rial)} ریال`)
|
||||||
|
}
|
||||||
|
if (portfolio.gold_rial_value != null && portfolio.gold_rial_value > 0) {
|
||||||
|
lines.push(`ارزش فعلی طلا: ${formatRialNumber(portfolio.gold_rial_value)} ریال`)
|
||||||
|
}
|
||||||
|
if (portfolio.unrealized_profit_rial != null) {
|
||||||
|
const pct = formatPortfolioProfitPercent(portfolio.unrealized_profit_percent)
|
||||||
|
lines.push(
|
||||||
|
`سود/زیان تحققنیافته: ${formatPortfolioProfitText(portfolio.unrealized_profit_rial)}${pct ? ` (${pct})` : ''}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('محاسبه بر اساس میانگین وزنی قیمت خرید و قیمت لحظهای بازار است.')
|
||||||
|
|
||||||
|
return lines.join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
/** متن توضیح مودال دارایی ریالی */
|
/** متن توضیح مودال دارایی ریالی */
|
||||||
export function formatRialAssetsInfoText(
|
export function formatRialAssetsInfoText(
|
||||||
cashWallet: number,
|
cashWallet: number,
|
||||||
|
|||||||
@@ -16,9 +16,21 @@
|
|||||||
'portfolio-overview__profit--down': profitTone === 'loss',
|
'portfolio-overview__profit--down': profitTone === 'loss',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<span class="portfolio-overview__profit-amount">{{ profitText }}</span>
|
<div class="portfolio-overview__profit-main">
|
||||||
<span v-if="profitPercent" class="portfolio-overview__profit-percent">{{ profitPercent }}</span>
|
<span class="portfolio-overview__profit-amount">{{ profitText }}</span>
|
||||||
<span class="portfolio-overview__profit-hint">سود/زیان تحققنیافته طلا</span>
|
<span v-if="profitPercent" class="portfolio-overview__profit-percent">{{ profitPercent }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="portfolio-overview__profit-label">
|
||||||
|
<span class="portfolio-overview__profit-hint">سود/زیان تحققنیافته طلا</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="portfolio-overview__profit-info"
|
||||||
|
aria-label="اطلاعات بیشتر"
|
||||||
|
@click="openProfitInfo"
|
||||||
|
>
|
||||||
|
<BasicIcon icon="vuesax-linear:info-circle" size="14" color="tertiary" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="portfolio-overview__grid">
|
<div class="portfolio-overview__grid">
|
||||||
@@ -36,17 +48,16 @@
|
|||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-if="hasProfitInsight && averageBuyPrice" class="portfolio-overview__footnote">
|
|
||||||
بر اساس میانگین خرید {{ usePriceWithoutType(averageBuyPrice) }} ریال و قیمت فعلی
|
|
||||||
{{ usePriceWithoutType(liveGoldPrice) }} ریال
|
|
||||||
</p>
|
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed, h } from 'vue'
|
||||||
|
import { Modal } from 'ant-design-vue'
|
||||||
|
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||||
import { useWalletAssets } from '@/composables/useWalletAssets'
|
import { useWalletAssets } from '@/composables/useWalletAssets'
|
||||||
import {
|
import {
|
||||||
|
formatPortfolioProfitInfoText,
|
||||||
formatPortfolioProfitLabel,
|
formatPortfolioProfitLabel,
|
||||||
formatPortfolioProfitPercent,
|
formatPortfolioProfitPercent,
|
||||||
formatPortfolioProfitText,
|
formatPortfolioProfitText,
|
||||||
@@ -79,8 +90,6 @@ const metalRial = computed(() => {
|
|||||||
return resolveAssetsRialValue(assetsSummary.value, getTradeBuyPrice(lastPrice.value))
|
return resolveAssetsRialValue(assetsSummary.value, getTradeBuyPrice(lastPrice.value))
|
||||||
})
|
})
|
||||||
const goldWeight = computed(() => Number(portfolio.value?.gold_weight ?? 0))
|
const goldWeight = computed(() => Number(portfolio.value?.gold_weight ?? 0))
|
||||||
const averageBuyPrice = computed(() => portfolio.value?.average_buy_price ?? null)
|
|
||||||
const liveGoldPrice = computed(() => Number(portfolio.value?.live_gold_price ?? 0))
|
|
||||||
|
|
||||||
const hasProfitInsight = computed(() => portfolio.value?.unrealized_profit_rial != null)
|
const hasProfitInsight = computed(() => portfolio.value?.unrealized_profit_rial != null)
|
||||||
const profitTone = computed(() => formatPortfolioProfitLabel(portfolio.value?.unrealized_profit_rial))
|
const profitTone = computed(() => formatPortfolioProfitLabel(portfolio.value?.unrealized_profit_rial))
|
||||||
@@ -89,6 +98,33 @@ const profitPercent = computed(() =>
|
|||||||
formatPortfolioProfitPercent(portfolio.value?.unrealized_profit_percent)
|
formatPortfolioProfitPercent(portfolio.value?.unrealized_profit_percent)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
function renderModalLines(text: string) {
|
||||||
|
return h(
|
||||||
|
'div',
|
||||||
|
{ style: { lineHeight: '1.9' } },
|
||||||
|
text.split('\n').map((line, index) =>
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{ key: index, style: index > 0 ? { marginTop: '6px' } : undefined },
|
||||||
|
line
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function openProfitInfo() {
|
||||||
|
const p = portfolio.value
|
||||||
|
if (!p) return
|
||||||
|
|
||||||
|
Modal.confirm({
|
||||||
|
title: 'سود/زیان تحققنیافته طلا',
|
||||||
|
content: renderModalLines(formatPortfolioProfitInfoText(p, weightUnit.value)),
|
||||||
|
okText: 'متوجه شدم',
|
||||||
|
centered: true,
|
||||||
|
cancelButtonProps: { style: { display: 'none' } },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const rootClass = computed(() => [
|
const rootClass = computed(() => [
|
||||||
'portfolio-overview',
|
'portfolio-overview',
|
||||||
`portfolio-overview--${props.variant}`,
|
`portfolio-overview--${props.variant}`,
|
||||||
@@ -146,17 +182,46 @@ const rootClass = computed(() => [
|
|||||||
|
|
||||||
.portfolio-overview__profit {
|
.portfolio-overview__profit {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-direction: column;
|
||||||
align-items: center;
|
gap: 6px;
|
||||||
gap: 8px;
|
padding: 10px 12px;
|
||||||
padding: 8px 10px;
|
border-radius: 12px;
|
||||||
border-radius: 10px;
|
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
background: rgba(255, 255, 255, 0.12);
|
background: rgba(255, 255, 255, 0.12);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.portfolio-overview__profit-main {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.portfolio-overview__profit-label {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.portfolio-overview__profit-info {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 0;
|
||||||
|
opacity: 0.85;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.portfolio-overview--desktop .portfolio-overview__profit {
|
.portfolio-overview--desktop .portfolio-overview__profit {
|
||||||
background: #f9fafb;
|
background: #f9fafb;
|
||||||
}
|
}
|
||||||
@@ -244,16 +309,4 @@ const rootClass = computed(() => [
|
|||||||
color: #9ca3af;
|
color: #9ca3af;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.portfolio-overview__footnote {
|
|
||||||
margin: 10px 0 0;
|
|
||||||
font-size: 11px;
|
|
||||||
line-height: 1.6;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.portfolio-overview--desktop .portfolio-overview__footnote {
|
|
||||||
color: #6b7280;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -10,25 +10,40 @@
|
|||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<span class="portfolio-profit__amount">{{ profitText }}</span>
|
<div class="portfolio-profit__main">
|
||||||
<span v-if="profitPercent" class="portfolio-profit__percent">{{ profitPercent }}</span>
|
<span class="portfolio-profit__amount">{{ profitText }}</span>
|
||||||
<span class="portfolio-profit__hint">سود/زیان تحققنیافته طلا</span>
|
<span v-if="profitPercent" class="portfolio-profit__percent">{{ profitPercent }}</span>
|
||||||
<p v-if="averageBuyPrice" class="portfolio-profit__footnote">
|
</div>
|
||||||
بر اساس میانگین خرید {{ usePriceWithoutType(averageBuyPrice) }} ریال و قیمت فعلی
|
|
||||||
{{ usePriceWithoutType(liveGoldPrice) }} ریال
|
<div class="portfolio-profit__label-row">
|
||||||
</p>
|
<span class="portfolio-profit__hint">سود/زیان تحققنیافته طلا</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="portfolio-profit__info"
|
||||||
|
aria-label="اطلاعات بیشتر"
|
||||||
|
@click="openInfoModal"
|
||||||
|
>
|
||||||
|
<BasicIcon
|
||||||
|
icon="vuesax-linear:info-circle"
|
||||||
|
:size="variant === 'desktop' ? 14 : 16"
|
||||||
|
:color="infoIconColor"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed, h } from 'vue'
|
||||||
|
import { Modal } from 'ant-design-vue'
|
||||||
|
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||||
import { useWalletAssets } from '@/composables/useWalletAssets'
|
import { useWalletAssets } from '@/composables/useWalletAssets'
|
||||||
import {
|
import {
|
||||||
|
formatPortfolioProfitInfoText,
|
||||||
formatPortfolioProfitLabel,
|
formatPortfolioProfitLabel,
|
||||||
formatPortfolioProfitPercent,
|
formatPortfolioProfitPercent,
|
||||||
formatPortfolioProfitText,
|
formatPortfolioProfitText,
|
||||||
} from '@/utils/productAssets'
|
} from '@/utils/productAssets'
|
||||||
import { usePriceWithoutType } from '@/utils/usePrice'
|
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@@ -40,8 +55,7 @@ const props = withDefaults(
|
|||||||
const { assetsSummary } = useWalletAssets()
|
const { assetsSummary } = useWalletAssets()
|
||||||
|
|
||||||
const portfolio = computed(() => assetsSummary.value?.portfolio)
|
const portfolio = computed(() => assetsSummary.value?.portfolio)
|
||||||
const averageBuyPrice = computed(() => portfolio.value?.average_buy_price ?? null)
|
const weightUnit = computed(() => assetsSummary.value?.meta?.weight_unit || 'گرم')
|
||||||
const liveGoldPrice = computed(() => Number(portfolio.value?.live_gold_price ?? 0))
|
|
||||||
|
|
||||||
const hasProfitInsight = computed(() => portfolio.value?.unrealized_profit_rial != null)
|
const hasProfitInsight = computed(() => portfolio.value?.unrealized_profit_rial != null)
|
||||||
const profitTone = computed(() => formatPortfolioProfitLabel(portfolio.value?.unrealized_profit_rial))
|
const profitTone = computed(() => formatPortfolioProfitLabel(portfolio.value?.unrealized_profit_rial))
|
||||||
@@ -49,81 +63,145 @@ const profitText = computed(() => formatPortfolioProfitText(portfolio.value?.unr
|
|||||||
const profitPercent = computed(() =>
|
const profitPercent = computed(() =>
|
||||||
formatPortfolioProfitPercent(portfolio.value?.unrealized_profit_percent)
|
formatPortfolioProfitPercent(portfolio.value?.unrealized_profit_percent)
|
||||||
)
|
)
|
||||||
|
const infoIconColor = computed(() => (props.variant === 'desktop' ? 'tertiary' : 'tertiary'))
|
||||||
|
|
||||||
|
function renderModalLines(text: string) {
|
||||||
|
return h(
|
||||||
|
'div',
|
||||||
|
{ style: { lineHeight: '1.9' } },
|
||||||
|
text.split('\n').map((line, index) =>
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{ key: index, style: index > 0 ? { marginTop: '6px' } : undefined },
|
||||||
|
line
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function openInfoModal() {
|
||||||
|
const p = portfolio.value
|
||||||
|
if (!p) return
|
||||||
|
|
||||||
|
Modal.confirm({
|
||||||
|
title: 'سود/زیان تحققنیافته طلا',
|
||||||
|
content: renderModalLines(formatPortfolioProfitInfoText(p, weightUnit.value)),
|
||||||
|
okText: 'متوجه شدم',
|
||||||
|
centered: true,
|
||||||
|
cancelButtonProps: { style: { display: 'none' } },
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.portfolio-profit {
|
.portfolio-profit {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
gap: 6px;
|
||||||
gap: 6px 8px;
|
padding: 10px 14px;
|
||||||
padding: 8px 10px;
|
border-radius: 12px;
|
||||||
border-radius: 10px;
|
margin-top: 10px;
|
||||||
margin-top: 8px;
|
background: linear-gradient(145deg, rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0.06));
|
||||||
background: rgba(255, 255, 255, 0.12);
|
border: 1px solid rgba(255, 255, 255, 0.22);
|
||||||
font-size: 13px;
|
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.1);
|
||||||
font-weight: 700;
|
backdrop-filter: blur(6px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.portfolio-profit--desktop {
|
.portfolio-profit--desktop {
|
||||||
justify-content: flex-start;
|
align-items: flex-start;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
background: #f9fafb;
|
background: linear-gradient(145deg, #f9fafb, #f3f4f6);
|
||||||
|
border: 1px solid rgba(15, 23, 42, 0.06);
|
||||||
|
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
|
||||||
|
backdrop-filter: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.portfolio-profit__main {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.portfolio-profit--desktop .portfolio-profit__main {
|
||||||
|
justify-content: flex-start;
|
||||||
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.portfolio-profit--up {
|
.portfolio-profit--up {
|
||||||
color: #bbf7d0;
|
color: #bbf7d0;
|
||||||
|
border-color: rgba(187, 247, 208, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
.portfolio-profit--desktop.portfolio-profit--up {
|
.portfolio-profit--desktop.portfolio-profit--up {
|
||||||
color: #027a48;
|
color: #027a48;
|
||||||
background: #ecfdf3;
|
background: linear-gradient(145deg, #ecfdf3, #d1fae5);
|
||||||
|
border-color: rgba(2, 122, 72, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.portfolio-profit--down {
|
.portfolio-profit--down {
|
||||||
color: #fecaca;
|
color: #fecaca;
|
||||||
|
border-color: rgba(254, 202, 202, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
.portfolio-profit--desktop.portfolio-profit--down {
|
.portfolio-profit--desktop.portfolio-profit--down {
|
||||||
color: #b42318;
|
color: #b42318;
|
||||||
background: #fef3f2;
|
background: linear-gradient(145deg, #fef3f2, #fee4e2);
|
||||||
|
border-color: rgba(180, 35, 24, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.portfolio-profit__percent {
|
.portfolio-profit__percent {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
unicode-bidi: isolate;
|
unicode-bidi: isolate;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
|
font-size: 0.92em;
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
|
||||||
|
.portfolio-profit__label-row {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.portfolio-profit--desktop .portfolio-profit__label-row {
|
||||||
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.portfolio-profit__hint {
|
.portfolio-profit__hint {
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
opacity: 0.85;
|
opacity: 0.88;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.portfolio-profit--desktop .portfolio-profit__hint {
|
.portfolio-profit--desktop .portfolio-profit__hint {
|
||||||
text-align: right;
|
|
||||||
color: #6b7280;
|
color: #6b7280;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.portfolio-profit__footnote {
|
.portfolio-profit__info {
|
||||||
width: 100%;
|
display: inline-flex;
|
||||||
margin: 4px 0 0;
|
align-items: center;
|
||||||
font-size: 10px;
|
justify-content: center;
|
||||||
font-weight: 500;
|
padding: 0;
|
||||||
line-height: 1.5;
|
margin: 0;
|
||||||
opacity: 0.75;
|
border: none;
|
||||||
text-align: center;
|
background: transparent;
|
||||||
}
|
cursor: pointer;
|
||||||
|
opacity: 0.85;
|
||||||
|
line-height: 0;
|
||||||
|
transition: opacity 0.15s ease;
|
||||||
|
|
||||||
.portfolio-profit--desktop .portfolio-profit__footnote {
|
&:hover {
|
||||||
text-align: right;
|
opacity: 1;
|
||||||
color: #6b7280;
|
}
|
||||||
opacity: 1;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user