fix some bugs
This commit is contained in:
@@ -2,7 +2,6 @@ import { computed, watch } from 'vue'
|
||||
import router from '@/router'
|
||||
import { authenticateWithFingerprint, getToken, isIphone, removeToken } from '@/utils'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { useWalletStore } from '@/store/walletStore'
|
||||
import { useFundUserStore } from '@/store/fundStore'
|
||||
import type { NavigationGuardNext } from 'vue-router'
|
||||
import { useAuthStore } from '@/store/useAuthStore'
|
||||
@@ -25,7 +24,6 @@ function goToHome(next: NavigationGuardNext) {
|
||||
|
||||
export function setupRouterGuards() {
|
||||
const userStore = useUserStore()
|
||||
const walletStore = useWalletStore()
|
||||
const fundStore = useFundUserStore()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
@@ -38,8 +36,6 @@ export function setupRouterGuards() {
|
||||
|
||||
async function start() {
|
||||
if (getToken()) {
|
||||
await walletStore.getLimitedPrice()
|
||||
await walletStore.getLimitedBuyGold()
|
||||
await fundStore.getMyFund()
|
||||
}
|
||||
}
|
||||
|
||||
+21
-11
@@ -2,6 +2,7 @@ import type { AppConfig } from '@/config/types'
|
||||
import { readCachedAppConfig, writeCachedAppConfig } from '@/config/config-cache'
|
||||
|
||||
const CONFIG_SUCCESS_URL_KEY = 'CONFIG_Success_URL'
|
||||
export const CONFIG_RETRY_ROUNDS = 5
|
||||
|
||||
function getProtocol() {
|
||||
return window.location.protocol === 'https:' ? 'https:' : 'http:'
|
||||
@@ -29,6 +30,12 @@ export function sortConfigUrls(urls: string[]) {
|
||||
})
|
||||
}
|
||||
|
||||
function clearStaleConfigSuccessUrl(url: string) {
|
||||
if (localStorage.getItem(CONFIG_SUCCESS_URL_KEY) === url) {
|
||||
localStorage.removeItem(CONFIG_SUCCESS_URL_KEY)
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchConfig(url: string, timeout = 3000): Promise<AppConfig> {
|
||||
const controller = new AbortController()
|
||||
const timeoutId = window.setTimeout(() => controller.abort(), timeout)
|
||||
@@ -59,22 +66,25 @@ async function fetchConfig(url: string, timeout = 3000): Promise<AppConfig> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchFreshAppConfig(): Promise<AppConfig> {
|
||||
export async function fetchFreshAppConfig(rounds = CONFIG_RETRY_ROUNDS): Promise<AppConfig> {
|
||||
const urls = sortConfigUrls(getConfigUrls())
|
||||
|
||||
for (let index = 0; index < urls.length; index++) {
|
||||
const url = urls[index]
|
||||
for (let round = 1; round <= rounds; round++) {
|
||||
for (let index = 0; index < urls.length; index++) {
|
||||
const url = urls[index]
|
||||
|
||||
try {
|
||||
const timeout = index === urls.length - 1 ? 5000 : 2500
|
||||
const config = await fetchConfig(url, timeout)
|
||||
try {
|
||||
const timeout = index === urls.length - 1 ? 7000 : 4000
|
||||
const config = await fetchConfig(url, timeout)
|
||||
|
||||
localStorage.setItem(CONFIG_SUCCESS_URL_KEY, url)
|
||||
writeCachedAppConfig(config)
|
||||
localStorage.setItem(CONFIG_SUCCESS_URL_KEY, url)
|
||||
writeCachedAppConfig(config)
|
||||
|
||||
return config
|
||||
} catch (error) {
|
||||
console.warn(`Config failed from ${url}`, error)
|
||||
return config
|
||||
} catch (error) {
|
||||
clearStaleConfigSuccessUrl(url)
|
||||
console.warn(`Config failed from ${url}. Round: ${round}`, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,16 +9,24 @@ type VersionResponse =
|
||||
}
|
||||
|
||||
const VERSION_SUCCESS_URL_KEY = 'VERSION_Success_URL'
|
||||
const VERSION_RETRY_ROUNDS = 5
|
||||
export const VERSION_RETRY_ROUNDS = 5
|
||||
|
||||
let versionFetchPromise: Promise<string | null> | null = null
|
||||
let cachedVersionResult: string | null | undefined
|
||||
|
||||
function getProtocol() {
|
||||
return window.location.protocol === 'https:' ? 'https:' : 'http:'
|
||||
}
|
||||
|
||||
function getVersionUrls() {
|
||||
const protocol = getProtocol()
|
||||
|
||||
return [
|
||||
'https://nerkh.gold/get_version',
|
||||
'https://tablo.talasevin.ir/get_version',
|
||||
'https://abshode-service.montgit.ir/server/get_version'
|
||||
'https://abshode-service.montgit.ir/server/get_version',
|
||||
`${protocol}//212.23.201.173:8381/get_version`,
|
||||
`${protocol}//91.212.174.137:4040/server/get_version`
|
||||
]
|
||||
}
|
||||
|
||||
@@ -33,6 +41,12 @@ function sortVersionUrls(urls: string[]) {
|
||||
return [lastSuccessUrl, ...others]
|
||||
}
|
||||
|
||||
function clearStaleVersionSuccessUrl(url: string) {
|
||||
if (localStorage.getItem(VERSION_SUCCESS_URL_KEY) === url) {
|
||||
localStorage.removeItem(VERSION_SUCCESS_URL_KEY)
|
||||
}
|
||||
}
|
||||
|
||||
function readUserVersion(user: unknown) {
|
||||
if (!user || typeof user !== 'object' || Array.isArray(user)) {
|
||||
return ''
|
||||
@@ -82,7 +96,6 @@ async function fetchVersion(url: string, timeout = 3500): Promise<string | null>
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
// Non-2xx/timeout should be treated as "request failed" and we may try the next URL.
|
||||
throw new Error(`Version request failed with status ${response.status}`)
|
||||
}
|
||||
|
||||
@@ -94,10 +107,8 @@ async function fetchVersion(url: string, timeout = 3500): Promise<string | null>
|
||||
|
||||
const version = normalizeVersionResponse(data)
|
||||
|
||||
// IMPORTANT: if the request returned 200/ok, we stop here even if we couldn't extract a version.
|
||||
// This prevents unnecessary looping when the endpoint is "reachable" but returns an empty payload.
|
||||
if (!version) {
|
||||
return null
|
||||
throw new Error('Version response is empty')
|
||||
}
|
||||
|
||||
localStorage.setItem(VERSION_SUCCESS_URL_KEY, url)
|
||||
@@ -118,10 +129,11 @@ async function fetchVersionWithRetry(rounds = VERSION_RETRY_ROUNDS): Promise<str
|
||||
|
||||
try {
|
||||
const version = await fetchVersion(url, timeout)
|
||||
// fetchVersion only throws on real request failures (non-ok / timeout).
|
||||
// If it returns null, we got an HTTP 200 but empty body => stop immediately.
|
||||
return version
|
||||
if (version) {
|
||||
return version
|
||||
}
|
||||
} catch (error) {
|
||||
clearStaleVersionSuccessUrl(url)
|
||||
console.warn(`Version failed from ${url}. Round: ${round}`, error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface Order {
|
||||
sts_str?: string
|
||||
payment_sts?: number
|
||||
payment_amount?: number
|
||||
target_cash?: number
|
||||
service_charge_price?: number
|
||||
total_fee?: number
|
||||
total_price?: number
|
||||
|
||||
@@ -35,6 +35,19 @@ export interface WalletMetalTotal {
|
||||
unit: string
|
||||
}
|
||||
|
||||
export interface WalletPortfolioInsight {
|
||||
total_rial_value: number
|
||||
cash_rial: number
|
||||
metal_rial_value: number
|
||||
gold_weight: number
|
||||
gold_rial_value: number
|
||||
average_buy_price?: number | null
|
||||
cost_basis_rial?: number | null
|
||||
unrealized_profit_rial?: number | null
|
||||
unrealized_profit_percent?: number | null
|
||||
live_gold_price: number
|
||||
}
|
||||
|
||||
export interface WalletAssetsSummary {
|
||||
meta: WalletAssetsMeta
|
||||
cash_wallet: number
|
||||
@@ -44,5 +57,6 @@ export interface WalletAssetsSummary {
|
||||
assets_rial_value: number
|
||||
total_rial_value: number
|
||||
/** boursecu — سود یا زیان کل دارایی (مثبت = سود، منفی = زیان) */
|
||||
profit_loss?: number
|
||||
profit_loss?: number | null
|
||||
portfolio?: WalletPortfolioInsight
|
||||
}
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import { type ComputedRef, ref } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import {defineStore} from "pinia";
|
||||
import {getMe} from "@/api/me";
|
||||
import type {User} from "@/models/login";
|
||||
import type {Wallet} from "@/models/wallet";
|
||||
import {getWallet} from "@/api/wallet";
|
||||
import type {Const} from "@/models/const";
|
||||
import {getConstByKey} from "@/api/const";
|
||||
export const useWalletStore = defineStore('wallet', () => {
|
||||
const walletInfo = ref<Wallet | undefined>(undefined)
|
||||
const limitedPrice = ref<Const[]>()
|
||||
const limitedBuyGold = ref<Const[]>()
|
||||
|
||||
async function getMyWallet() {
|
||||
try {
|
||||
@@ -19,40 +13,6 @@ export const useWalletStore = defineStore('wallet', () => {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
async function getLimitedPrice(){
|
||||
try {
|
||||
limitedPrice.value = await getConstByKey('limited_price')
|
||||
return limitedPrice.value
|
||||
}catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
async function getLimitedBuyGold(){
|
||||
try {
|
||||
limitedBuyGold.value = await getConstByKey('first_customer_wallet_value')
|
||||
return limitedBuyGold.value
|
||||
}catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
/* const permissions: ComputedRef<string[]> = computed(() => {
|
||||
const permissions: string[] = get(userInfo.value, 'permission', [])
|
||||
const excludes: string[] = get(userInfo.value, 'exclude', [])
|
||||
return permissions.filter((item) => !excludes.includes(item))
|
||||
})*/
|
||||
|
||||
/*
|
||||
function canAccess(permissionKey: string) {
|
||||
if (isAdmin.value) return true
|
||||
return permissions.value.includes(permissionKey)
|
||||
}
|
||||
*/
|
||||
/*
|
||||
const isAdmin = computed(() => {
|
||||
return userInfo.value?.is_admin
|
||||
})*/
|
||||
|
||||
return { walletInfo, getMyWallet, getLimitedPrice, getLimitedBuyGold, limitedPrice, limitedBuyGold/* permissions, canAccess, isAdmin*/ }
|
||||
return { walletInfo, getMyWallet }
|
||||
})
|
||||
|
||||
@@ -209,7 +209,7 @@ function formatRialNumber(value: number): string {
|
||||
return usePriceWithoutType(Math.round(value))
|
||||
}
|
||||
|
||||
function formatBoursecuProfitLossLine(profitLoss?: number): BoursecuAssetPopupLine {
|
||||
function formatBoursecuProfitLossLine(profitLoss?: number | null): BoursecuAssetPopupLine {
|
||||
const amount = Number(profitLoss ?? 0)
|
||||
|
||||
if (amount > 0) {
|
||||
@@ -398,14 +398,66 @@ export function formatMetalAssetsInfoText(metal: WalletMetalTotal): string {
|
||||
return `${metal.label}: مجموع کیف محصولات (${metal.product_weight} ${unit}) + موجودی کیف اصلی (${metal.melted_weight} ${unit})`
|
||||
}
|
||||
|
||||
/** نمایش سود/زیان برای داشبورد */
|
||||
export function formatPortfolioProfitLabel(profitRial?: number | null): 'profit' | 'loss' | 'neutral' {
|
||||
const amount = Number(profitRial ?? 0)
|
||||
if (amount > 0) return 'profit'
|
||||
if (amount < 0) return 'loss'
|
||||
return 'neutral'
|
||||
}
|
||||
|
||||
export function formatPortfolioProfitText(profitRial?: number | null): string {
|
||||
const amount = Number(profitRial ?? 0)
|
||||
if (amount > 0) {
|
||||
return `+${formatRialNumber(amount)} ریال سود`
|
||||
}
|
||||
if (amount < 0) {
|
||||
return `${formatRialNumber(Math.abs(amount))} ریال زیان`
|
||||
}
|
||||
return `${formatRialNumber(0)} ریال`
|
||||
}
|
||||
|
||||
export function formatPortfolioProfitPercent(percent?: number | null): string {
|
||||
if (percent == null || Number.isNaN(Number(percent))) {
|
||||
return ''
|
||||
}
|
||||
const value = Number(percent)
|
||||
const sign = value > 0 ? '+' : ''
|
||||
return `${sign}${value.toFixed(2)}٪`
|
||||
}
|
||||
|
||||
/** متن توضیح مودال دارایی ریالی */
|
||||
export function formatRialAssetsInfoText(cashWallet: number, metalRialValue: number): string {
|
||||
export function formatRialAssetsInfoText(
|
||||
cashWallet: number,
|
||||
metalRialValue: number,
|
||||
portfolio?: {
|
||||
unrealized_profit_rial?: number | null
|
||||
unrealized_profit_percent?: number | null
|
||||
average_buy_price?: number | null
|
||||
cost_basis_rial?: number | null
|
||||
} | null
|
||||
): string {
|
||||
const total = cashWallet + metalRialValue
|
||||
return [
|
||||
const lines = [
|
||||
`کیف پول ریالی: ${formatRialNumber(cashWallet)} ریال`,
|
||||
`ارزش ریالی طلا (وزن × قیمت زنده): ${formatRialNumber(metalRialValue)} ریال`,
|
||||
`مجموع: ${formatRialNumber(total)} ریال`,
|
||||
].join('\n')
|
||||
`مجموع کل دارایی: ${formatRialNumber(total)} ریال`,
|
||||
]
|
||||
|
||||
if (portfolio?.average_buy_price != null && portfolio.average_buy_price > 0) {
|
||||
lines.push(`میانگین قیمت خرید شما: ${formatRialNumber(portfolio.average_buy_price)} ریال`)
|
||||
}
|
||||
if (portfolio?.cost_basis_rial != null && portfolio.cost_basis_rial > 0) {
|
||||
lines.push(`بهای تمامشده طلا: ${formatRialNumber(portfolio.cost_basis_rial)} ریال`)
|
||||
}
|
||||
if (portfolio?.unrealized_profit_rial != null) {
|
||||
const pct = formatPortfolioProfitPercent(portfolio.unrealized_profit_percent)
|
||||
lines.push(
|
||||
`سود/زیان تحققنیافته: ${formatPortfolioProfitText(portfolio.unrealized_profit_rial)}${pct ? ` (${pct})` : ''}`
|
||||
)
|
||||
}
|
||||
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
/** ارزش ریالی فلزات از وزن × قیمت زنده — fallback کلاینت قبل از deploy بکاند */
|
||||
|
||||
@@ -3,6 +3,9 @@ import { getSettings } from '@/api/configuration'
|
||||
|
||||
export const serviceFeePercentage = ref<number>(0.01)
|
||||
|
||||
/** true = مبالغ رند: فروش → ریال دقیق (کارمزد از طلا)؛ خرید → وزن دقیق (کارمزد از ریال) */
|
||||
export const feeDeductFromGold = ref(false)
|
||||
|
||||
export async function loadServiceFeeFromSettings() {
|
||||
try {
|
||||
const settings = await getSettings()
|
||||
@@ -13,6 +16,16 @@ export async function loadServiceFeeFromSettings() {
|
||||
if (feeSetting?.value !== undefined && feeSetting?.value !== null) {
|
||||
serviceFeePercentage.value = Number(feeSetting.value)
|
||||
}
|
||||
|
||||
const feeModeSetting = settings.find(
|
||||
(s) => s?.key === 'fee_deduct_from_gold' && s?.is_active === true
|
||||
)
|
||||
if (feeModeSetting?.value !== undefined && feeModeSetting?.value !== null) {
|
||||
const raw = String(feeModeSetting.value).trim().toLowerCase()
|
||||
feeDeductFromGold.value = raw === 'true' || raw === '1'
|
||||
} else {
|
||||
feeDeductFromGold.value = false
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
@@ -22,3 +35,70 @@ export async function loadServiceFeeFromSettings() {
|
||||
export function calcFeeInGrams(goldValue?: number | null) {
|
||||
return parseFloat(((goldValue ?? 0) * Number(serviceFeePercentage.value)).toFixed(4))
|
||||
}
|
||||
|
||||
/** گرم معادل برای دریافت دقیق مبلغ ریالی در فروش */
|
||||
export function calcGoldAmountFromTargetRial(rialAmount: number, unitPriceRial: number) {
|
||||
if (!rialAmount || !unitPriceRial) return 0
|
||||
return parseFloat((rialAmount / unitPriceRial).toFixed(6))
|
||||
}
|
||||
|
||||
/** مبلغ نهایی قابل پرداخت (خرید) یا دریافت (فروش) به ریال */
|
||||
export function calcPayablePriceRial(
|
||||
finalPriceRial: number,
|
||||
feeInRial: number,
|
||||
isBuy: boolean,
|
||||
targetRialCash?: number | null
|
||||
) {
|
||||
if (feeDeductFromGold.value) {
|
||||
if (isBuy) return finalPriceRial + feeInRial
|
||||
if (targetRialCash != null && Number(targetRialCash) > 0) return Number(targetRialCash)
|
||||
return finalPriceRial
|
||||
}
|
||||
return isBuy ? finalPriceRial : Math.max(0, finalPriceRial - feeInRial)
|
||||
}
|
||||
|
||||
/** وزن ارسالی به سرور — فروش رند: از مبلغ ریالی هدف */
|
||||
export function calcOrderGoldAmount(
|
||||
goldValue?: number | null,
|
||||
goldCash?: number | null,
|
||||
unitPriceRial?: number,
|
||||
isBuy?: boolean
|
||||
) {
|
||||
const gold = Number(goldValue ?? 0)
|
||||
if (isBuy || !feeDeductFromGold.value) {
|
||||
return parseFloat(gold.toFixed(6))
|
||||
}
|
||||
const targetCash = calcSellTargetCash(goldCash, goldValue, unitPriceRial)
|
||||
const unit = Number(unitPriceRial ?? 0)
|
||||
if (targetCash != null && unit > 0) {
|
||||
return calcGoldAmountFromTargetRial(targetCash, unit)
|
||||
}
|
||||
return parseFloat(gold.toFixed(6))
|
||||
}
|
||||
|
||||
/** مبلغ ریالی دقیق دریافتی در فروش (فقط وقتی کاربر با ریال وارد کرده) */
|
||||
export function calcSellTargetCash(
|
||||
goldCash?: number | null,
|
||||
goldValue?: number | null,
|
||||
unitPriceRial?: number
|
||||
) {
|
||||
const cash = Number(goldCash ?? 0)
|
||||
if (!feeDeductFromGold.value || cash <= 0) return undefined
|
||||
const gold = Number(goldValue ?? 0)
|
||||
const unit = Number(unitPriceRial ?? 0)
|
||||
if (gold > 0 && unit > 0) {
|
||||
const derived = Math.round(gold * unit)
|
||||
if (Math.abs(derived - Math.round(cash)) <= 1) return undefined
|
||||
}
|
||||
return Math.round(cash)
|
||||
}
|
||||
|
||||
/** کل طلای کسرشونده از کیف در فروش (شامل کارمزد وقتی fee_deduct_from_gold روشن است) */
|
||||
export function calcSellGoldDebit(goldValue?: number | null, feeInGrams?: number | null) {
|
||||
const gold = Number(goldValue ?? 0)
|
||||
const fee = Number(feeInGrams ?? 0)
|
||||
if (feeDeductFromGold.value) {
|
||||
return parseFloat((gold + fee).toFixed(4))
|
||||
}
|
||||
return gold
|
||||
}
|
||||
|
||||
@@ -36,11 +36,6 @@
|
||||
<div v-else class="w-full h-[88px]">
|
||||
<GoldSkeleton class="w-full" />
|
||||
</div>
|
||||
<div v-if="limitedPrice !== undefined && userStore?.userInfo.trade_active !== 1"
|
||||
class="text-red text-xs flex gap-1 items-center justify-center my-2">
|
||||
<Icon icon="vuesax-linear:info-circle" size="16" />
|
||||
حداقل مبلغ اولیه برای ثبت سفارش {{ usePriceWithoutType(limitedPrice.value as number ?? 0) }} ریال می باشد.
|
||||
</div>
|
||||
</a-spin>
|
||||
<a-form :model="form" class="!mt-5" name="basic" @finish="okConfirm()">
|
||||
<div class="border-gray-200 border-1 rounded-xl p-5 h-58 relative">
|
||||
@@ -236,7 +231,7 @@ import { getLastPrices, getTradeBuyPrice, getTradeSellPrice, lastPrice } from '@
|
||||
import type { Product } from '@/models/products'
|
||||
import { useFundUserStore } from '@/store/fundStore'
|
||||
import {useConfigStore} from "@/store/configStore";
|
||||
import { calcFeeInGrams } from '@/utils/serviceFee'
|
||||
import { calcFeeInGrams, calcPayablePriceRial, calcSellGoldDebit, feeDeductFromGold, loadServiceFeeFromSettings } from '@/utils/serviceFee'
|
||||
import { formatCashWalletBalanceText, formatGoldWalletBalanceText } from '@/utils/productDealMethod'
|
||||
import { getCashWalletBalance } from '@/utils/productAssets'
|
||||
import { isStoreClosed, getStoreClosedMessage } from '@/utils/storeHours'
|
||||
@@ -315,8 +310,9 @@ const finalPrice = computed(() => {
|
||||
})
|
||||
|
||||
const payablePrice = computed(() =>
|
||||
isBuy.value ? finalPrice.value + feeInRial.value : finalPrice.value - feeInRial.value
|
||||
calcPayablePriceRial(finalPrice.value, feeInRial.value, isBuy.value)
|
||||
)
|
||||
const sellGoldDebit = computed(() => calcSellGoldDebit(form.value?.goldValue, feeInGrams.value))
|
||||
const feeInGrams = computed(() => calcFeeInGrams(form.value?.goldValue))
|
||||
const unitPrice = computed(() =>
|
||||
(props?.ProductItem?.price_profit ?? 0) + (props?.ProductItem?.price ?? 0)
|
||||
@@ -332,7 +328,8 @@ const disableTransaction = computed(() =>
|
||||
form.value?.goldCash === '' ||
|
||||
form.value?.goldCash === undefined ||
|
||||
(isBuy.value ? (form.value?.goldCash ?? 0) < 500000 : (form.value?.goldValue ?? 0) < 0.001) ||
|
||||
(!isBuy.value && form.value.goldValue > walletStore?.walletInfo?.gold) ||
|
||||
(!isBuy.value && sellGoldDebit.value > (walletStore?.walletInfo?.gold ?? 0)) ||
|
||||
(isBuy.value && payablePrice.value > cashWalletBalance.value) ||
|
||||
!lastPrice.value ||
|
||||
(isBuy.value ? getTradeBuyPrice(lastPrice.value) <= 0 : getTradeSellPrice(lastPrice.value) <= 0))
|
||||
|
||||
@@ -436,6 +433,7 @@ onMounted(async () => {
|
||||
break
|
||||
}
|
||||
await initPrice()
|
||||
await loadServiceFeeFromSettings()
|
||||
/* await initSellTable()
|
||||
await initBuyTable()*/
|
||||
})
|
||||
@@ -499,11 +497,6 @@ const unitCashCalculator = computed(() => {
|
||||
return cashWalletBalance.value / 100
|
||||
})
|
||||
|
||||
const limitedPrice = computed(() => {
|
||||
if (walletStore.limitedPrice)
|
||||
return walletStore?.limitedPrice[0]
|
||||
})
|
||||
|
||||
/*function calculateCash(val: number) {
|
||||
priceErrorVisible.value = false
|
||||
// console.log(val)
|
||||
|
||||
@@ -56,11 +56,6 @@
|
||||
<div v-else class="w-full h-[88px]">
|
||||
<GoldSkeleton class="w-full" />
|
||||
</div>
|
||||
<div v-if="limitedPrice !== undefined && userStore?.userInfo.trade_active !== 1"
|
||||
class="text-red text-xs flex gap-1 items-center justify-center my-2">
|
||||
<Icon icon="vuesax-linear:info-circle" size="16" />
|
||||
حداقل مبلغ اولیه برای ثبت سفارش {{ usePriceWithoutType(limitedPrice.value as number ?? 0) }} ریال می باشد.
|
||||
</div>
|
||||
</a-spin>
|
||||
<a-form :model="form" class="!mt-5" name="basic" @finish="okConfirm()">
|
||||
<div class="border-gray-200 border-1 rounded-xl p-5 gold-form-card relative">
|
||||
@@ -137,13 +132,14 @@
|
||||
<div v-else>{{ !isBuy ? 'فروش' : 'خرید' }}</div>
|
||||
</div>
|
||||
</BasicButton>
|
||||
<div v-if="!isBuy && form.goldValue > productWalletBalance"
|
||||
<div v-if="!isBuy && sellGoldDebit > productWalletBalance"
|
||||
class="text-red-700 text-xs font-bold mt-2">
|
||||
مقدار {{ weightUnit }} وارد شده جهت فروش ({{ form.goldValue }}{{ weightUnit }}) بیشتر از مقدار موجودی شما
|
||||
({{ productWalletBalance }}{{ weightUnit }}) می باشد.
|
||||
<span v-if="feeDeductFromGold"> (با احتساب {{ feeInGrams }}{{ weightUnit }} کارمزد، مجموعاً {{ sellGoldDebit }}{{ weightUnit }} کسر میشود)</span>
|
||||
</div>
|
||||
<!-- <div>{{form?.goldValue? true: false}}</div>-->
|
||||
<div v-if="isBuy ? (form?.goldCash > cashWalletBalance) : false"
|
||||
<div v-if="isBuy ? (payablePrice > cashWalletBalance) : false"
|
||||
class="text-red-700 text-xs font-bold mt-2">
|
||||
موجودی کیف پول ناکافی می باشد.
|
||||
</div>
|
||||
@@ -196,6 +192,20 @@
|
||||
</div>
|
||||
</div>
|
||||
<div >(معادل {{ equivalentPrice }} ریال)</div>
|
||||
<div v-if="isBuy && feeDeductFromGold" class="flex justify-between mt-3">
|
||||
<div>طلا دریافتی:</div>
|
||||
<div class="flex items-center">
|
||||
<div class="font-bold">{{ form.goldValue }}</div>
|
||||
<div class="text-xs mr-1">{{ weightUnit }} (دقیق — کارمزد از ریال)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!isBuy && feeDeductFromGold" class="flex justify-between mt-3">
|
||||
<div>کسر از کیف طلا:</div>
|
||||
<div class="flex items-center">
|
||||
<div class="font-bold">{{ sellGoldDebit }}</div>
|
||||
<div class="text-xs mr-1">{{ weightUnit }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between mt-3">
|
||||
<div>{{ isBuy ? 'قیمت قابل پرداخت:' : 'مبلغ دریافتی:' }}</div>
|
||||
<div class="flex items-center">
|
||||
@@ -237,6 +247,20 @@
|
||||
</div>
|
||||
</div>
|
||||
<div>(معادل {{ equivalentPrice }} ریال)</div>
|
||||
<div v-if="isBuy && feeDeductFromGold" class="flex justify-between mt-3">
|
||||
<div>طلا دریافتی:</div>
|
||||
<div class="flex items-center">
|
||||
<div class="font-bold">{{ form.goldValue }}</div>
|
||||
<div class="text-xs mr-1">{{ weightUnit }} (دقیق — کارمزد از ریال)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!isBuy && feeDeductFromGold" class="flex justify-between mt-3">
|
||||
<div>کسر از کیف طلا:</div>
|
||||
<div class="flex items-center">
|
||||
<div class="font-bold">{{ sellGoldDebit }}</div>
|
||||
<div class="text-xs mr-1">{{ weightUnit }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between mt-3">
|
||||
<div>{{ isBuy ? 'قیمت قابل پرداخت:' : 'مبلغ دریافتی:' }}</div>
|
||||
<div class="flex items-center">
|
||||
@@ -306,7 +330,7 @@ import { getLastPrices, getTradeBuyPrice, getTradeSellPrice, lastPrice } from '@
|
||||
import type { Product } from '@/models/products'
|
||||
import { useFundUserStore } from '@/store/fundStore'
|
||||
import {useConfigStore} from "@/store/configStore";
|
||||
import { calcFeeInGrams, loadServiceFeeFromSettings } from '@/utils/serviceFee'
|
||||
import { calcFeeInGrams, calcOrderGoldAmount, calcPayablePriceRial, calcSellGoldDebit, calcSellTargetCash, feeDeductFromGold, loadServiceFeeFromSettings } from '@/utils/serviceFee'
|
||||
import { isStoreClosed, getStoreClosedMessage } from '@/utils/storeHours'
|
||||
import { formatCashWalletBalanceText, getWeightUnit } from '@/utils/productDealMethod'
|
||||
import { formatProductWalletBalanceText, getCashWalletBalance, getProductInventoryQuantity } from '@/utils/productAssets'
|
||||
@@ -411,13 +435,22 @@ const unitPrice = computed(() => {
|
||||
}
|
||||
return isBuy.value ? getTradeBuyPrice(lastPrice.value) : getTradeSellPrice(lastPrice.value)
|
||||
})
|
||||
const feeInGrams = computed(() => calcFeeInGrams(form.value?.goldValue))
|
||||
const finalPrice = computed(() => (form.value?.goldValue ?? 0) * unitPrice.value)
|
||||
const orderGoldAmount = computed(() =>
|
||||
calcOrderGoldAmount(form.value?.goldValue, form.value?.goldCash, unitPrice.value, isBuy.value)
|
||||
)
|
||||
const feeInGrams = computed(() => calcFeeInGrams(orderGoldAmount.value))
|
||||
const finalPrice = computed(() => orderGoldAmount.value * unitPrice.value)
|
||||
const feeInRial = computed(() => unitPrice.value * feeInGrams.value)
|
||||
const equivalentPrice = computed(() => usePriceWithoutType(feeInRial.value.toFixed(0)))
|
||||
const payablePrice = computed(() =>
|
||||
isBuy.value ? finalPrice.value + feeInRial.value : finalPrice.value - feeInRial.value
|
||||
calcPayablePriceRial(
|
||||
finalPrice.value,
|
||||
feeInRial.value,
|
||||
isBuy.value,
|
||||
!isBuy.value ? form.value?.goldCash : undefined
|
||||
)
|
||||
)
|
||||
const sellGoldDebit = computed(() => calcSellGoldDebit(orderGoldAmount.value, feeInGrams.value))
|
||||
|
||||
const minOrderCash = computed(() => productMinOrderCash(props.ProductItem, isBuy.value))
|
||||
const maxOrderCash = computed(() => productMaxOrderCash(props.ProductItem))
|
||||
@@ -444,7 +477,8 @@ const disableTransaction = computed(() =>
|
||||
: (form.value?.goldValue ?? 0) < 0.001)) ||
|
||||
(maxOrderCash.value != null && (form.value?.goldCash ?? 0) > maxOrderCash.value) ||
|
||||
cashValidationMessage.value !== '' ||
|
||||
(!isBuy.value && form.value.goldValue > walletStore?.walletInfo?.gold) ||
|
||||
(!isBuy.value && sellGoldDebit.value > productWalletBalance.value) ||
|
||||
(isBuy.value && payablePrice.value > cashWalletBalance.value) ||
|
||||
priceUnavailable.value)
|
||||
|
||||
function refreshTable() {
|
||||
@@ -609,11 +643,6 @@ const unitCashCalculator = computed(() => {
|
||||
return cashWalletBalance.value / 100
|
||||
})
|
||||
|
||||
const limitedPrice = computed(() => {
|
||||
if (walletStore.limitedPrice)
|
||||
return walletStore?.limitedPrice[0]
|
||||
})
|
||||
|
||||
function syncFormWithLatestPrice() {
|
||||
const unit = unitPrice.value
|
||||
if (!unit || Number.isNaN(unit)) return
|
||||
@@ -661,7 +690,11 @@ function calculateGold(val: number) {
|
||||
priceErrorVisible.value = false
|
||||
}
|
||||
} else if (val) {
|
||||
form.value.goldValue = toFixedDownThreeChar(parseFloat(String(val / unit)))
|
||||
if (!isBuy.value && feeDeductFromGold.value) {
|
||||
form.value.goldValue = parseFloat((val / unit).toFixed(6))
|
||||
} else {
|
||||
form.value.goldValue = toFixedDownThreeChar(parseFloat(String(val / unit)))
|
||||
}
|
||||
priceErrorVisible.value = parseInt(String(finalPrice.value)) > 0
|
||||
} else if (val.toString() === '') {
|
||||
form.value.goldValue = undefined
|
||||
@@ -744,7 +777,8 @@ async function ok() {
|
||||
await createOrder({
|
||||
direction: isBuy.value ? 1 : 2,
|
||||
price: Math.round(unitPrice.value),
|
||||
amount: form.value.goldValue ?? 0,
|
||||
amount: orderGoldAmount.value,
|
||||
target_cash: calcSellTargetCash(form.value.goldCash, form.value.goldValue, unitPrice.value),
|
||||
abshode_id: props.ProductItem?.id,
|
||||
})
|
||||
confirmAction.value = false
|
||||
|
||||
@@ -3,19 +3,21 @@
|
||||
<div
|
||||
v-if="!boursecuLayout && meta?.has_count_based_products"
|
||||
:class="rowClass"
|
||||
class="flex justify-center w-full z-20"
|
||||
class="flex justify-center w-full z-20 mb-2"
|
||||
>
|
||||
محصولات:
|
||||
<div class="text-l mr-2 font-bold">{{ usePriceWithoutType(countBasedInventory, false) }} عدد</div>
|
||||
</div>
|
||||
|
||||
<PortfolioOverview v-if="!boursecuLayout" variant="mobile" />
|
||||
|
||||
<BoursecuAssetsSummary
|
||||
v-if="boursecuLayout"
|
||||
v-else-if="boursecuLayout"
|
||||
variant="mobile"
|
||||
icon-color="primary"
|
||||
/>
|
||||
|
||||
<template v-else-if="meta?.single_metal && primaryMetalTotal">
|
||||
<template v-else-if="showLegacyRows && meta?.single_metal && primaryMetalTotal">
|
||||
<div :class="[rowClass, meta?.has_count_based_products ? 'mt-2' : '']" class="flex justify-center w-full z-20">
|
||||
{{ primaryMetalTotal.label }}:
|
||||
<div class="text-l mr-2 font-bold">{{ formatMetalTotal(primaryMetalTotal) }}</div>
|
||||
@@ -39,7 +41,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="!boursecuLayout">
|
||||
<template v-else-if="showLegacyRows && !boursecuLayout">
|
||||
<div
|
||||
v-for="(metal, index) in metalTotals"
|
||||
:key="metal.metal_id"
|
||||
@@ -75,6 +77,7 @@
|
||||
import { computed } from 'vue'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import BoursecuAssetsSummary from './BoursecuAssetsSummary.vue'
|
||||
import PortfolioOverview from './PortfolioOverview.vue'
|
||||
import { formatWeightAmount, usePriceWithoutType } from '@/utils/usePrice'
|
||||
import { useWalletAssets } from '@/composables/useWalletAssets'
|
||||
import type { WalletMetalTotal } from '@/models/walletAssets'
|
||||
@@ -116,6 +119,7 @@ const inventoryCards = computed(() => assetsSummary.value?.inventory_cards ?? []
|
||||
const countBasedInventory = computed(() =>
|
||||
(assetsSummary.value?.count_products ?? []).reduce((sum, item) => sum + Number(item.quantity ?? 0), 0)
|
||||
)
|
||||
const showLegacyRows = computed(() => !assetsSummary.value?.portfolio)
|
||||
|
||||
function formatMetalTotal(metal: WalletMetalTotal) {
|
||||
return `${formatWeightAmount(resolveSummaryMetalWeight(metal, inventoryCards.value))}${metal.unit}`
|
||||
|
||||
@@ -42,6 +42,21 @@
|
||||
is-line
|
||||
:value="usePrice(totalRialValue)"
|
||||
/>
|
||||
<template v-if="portfolioInsight?.unrealized_profit_rial != null">
|
||||
<a-divider class="!my-1" />
|
||||
<BasicInfoItem
|
||||
class="flex justify-between"
|
||||
title="میانگین قیمت خرید"
|
||||
is-line
|
||||
:value="usePrice(portfolioInsight.average_buy_price ?? 0)"
|
||||
/>
|
||||
<BasicInfoItem
|
||||
class="flex justify-between"
|
||||
title="سود/زیان تحققنیافته"
|
||||
is-line
|
||||
:value="profitSheetLabel"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -56,7 +71,7 @@ import BasicInfoItem from '@/components/BasicPacks/BasicInfoItem.vue'
|
||||
import { usePrice } from '@/utils/usePrice'
|
||||
import { useWalletAssets } from '@/composables/useWalletAssets'
|
||||
import type { WalletMetalTotal } from '@/models/walletAssets'
|
||||
import { formatInventoryCardValue, assetSheetInventoryCards, resolveAssetsRialValue } from '@/utils/productAssets'
|
||||
import { formatInventoryCardValue, assetSheetInventoryCards, resolveAssetsRialValue, formatPortfolioProfitPercent, formatPortfolioProfitText } from '@/utils/productAssets'
|
||||
import { getTradeBuyPrice, lastPrice } from '@/utils/price'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -83,6 +98,13 @@ const metalRialValue = computed(() =>
|
||||
resolveAssetsRialValue(assetsSummary.value, getTradeBuyPrice(lastPrice.value))
|
||||
)
|
||||
const totalRialValue = computed(() => cashWallet.value + metalRialValue.value)
|
||||
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})` : ''}`
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
<template>
|
||||
<section :class="rootClass">
|
||||
<header class="portfolio-overview__header">
|
||||
<span class="portfolio-overview__eyebrow">کل دارایی شما</span>
|
||||
<p class="portfolio-overview__total">
|
||||
{{ usePriceWithoutType(totalRial) }}
|
||||
<span class="portfolio-overview__unit">ریال</span>
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div
|
||||
v-if="hasProfitInsight"
|
||||
class="portfolio-overview__profit"
|
||||
:class="{
|
||||
'portfolio-overview__profit--up': profitTone === 'profit',
|
||||
'portfolio-overview__profit--down': profitTone === 'loss',
|
||||
}"
|
||||
>
|
||||
<span class="portfolio-overview__profit-amount">{{ profitText }}</span>
|
||||
<span v-if="profitPercent" class="portfolio-overview__profit-percent">{{ profitPercent }}</span>
|
||||
<span class="portfolio-overview__profit-hint">سود/زیان تحققنیافته طلا</span>
|
||||
</div>
|
||||
|
||||
<div class="portfolio-overview__grid">
|
||||
<article class="portfolio-overview__tile">
|
||||
<span class="portfolio-overview__tile-label">ارزش طلا</span>
|
||||
<strong class="portfolio-overview__tile-value">{{ usePriceWithoutType(metalRial) }} ریال</strong>
|
||||
<span v-if="goldWeight > 0" class="portfolio-overview__tile-sub">
|
||||
{{ formatWeightAmount(goldWeight) }}{{ weightUnit }}
|
||||
</span>
|
||||
</article>
|
||||
<article class="portfolio-overview__tile">
|
||||
<span class="portfolio-overview__tile-label">موجودی نقد</span>
|
||||
<strong class="portfolio-overview__tile-value">{{ usePriceWithoutType(cashRial) }} ریال</strong>
|
||||
<span class="portfolio-overview__tile-sub">کیف پول ریالی</span>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<p v-if="hasProfitInsight && averageBuyPrice" class="portfolio-overview__footnote">
|
||||
بر اساس میانگین خرید {{ usePriceWithoutType(averageBuyPrice) }} ریال و قیمت فعلی
|
||||
{{ usePriceWithoutType(liveGoldPrice) }} ریال
|
||||
</p>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useWalletAssets } from '@/composables/useWalletAssets'
|
||||
import {
|
||||
formatPortfolioProfitLabel,
|
||||
formatPortfolioProfitPercent,
|
||||
formatPortfolioProfitText,
|
||||
resolveAssetsRialValue,
|
||||
} from '@/utils/productAssets'
|
||||
import { formatWeightAmount, usePriceWithoutType } from '@/utils/usePrice'
|
||||
import { getTradeBuyPrice, lastPrice } from '@/utils/price'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
variant?: 'mobile' | 'desktop'
|
||||
}>(),
|
||||
{ variant: 'mobile' }
|
||||
)
|
||||
|
||||
const { assetsSummary } = useWalletAssets()
|
||||
|
||||
const portfolio = computed(() => assetsSummary.value?.portfolio)
|
||||
const weightUnit = computed(() => assetsSummary.value?.meta?.weight_unit || 'گرم')
|
||||
|
||||
const totalRial = computed(() =>
|
||||
Number(portfolio.value?.total_rial_value ?? assetsSummary.value?.total_rial_value ?? 0)
|
||||
)
|
||||
const cashRial = computed(() =>
|
||||
Number(portfolio.value?.cash_rial ?? assetsSummary.value?.cash_wallet ?? 0)
|
||||
)
|
||||
const metalRial = computed(() => {
|
||||
const fromPortfolio = Number(portfolio.value?.metal_rial_value ?? 0)
|
||||
if (fromPortfolio > 0) return fromPortfolio
|
||||
return resolveAssetsRialValue(assetsSummary.value, getTradeBuyPrice(lastPrice.value))
|
||||
})
|
||||
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 profitTone = computed(() => formatPortfolioProfitLabel(portfolio.value?.unrealized_profit_rial))
|
||||
const profitText = computed(() => formatPortfolioProfitText(portfolio.value?.unrealized_profit_rial))
|
||||
const profitPercent = computed(() =>
|
||||
formatPortfolioProfitPercent(portfolio.value?.unrealized_profit_percent)
|
||||
)
|
||||
|
||||
const rootClass = computed(() => [
|
||||
'portfolio-overview',
|
||||
`portfolio-overview--${props.variant}`,
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.portfolio-overview {
|
||||
border-radius: 14px;
|
||||
padding: 14px 16px;
|
||||
background: linear-gradient(145deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0.06));
|
||||
border: 1px solid rgba(255, 255, 255, 0.22);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.portfolio-overview--desktop {
|
||||
margin-bottom: 16px;
|
||||
padding: 20px 22px;
|
||||
background: #fff;
|
||||
border: 1px solid rgba(15, 23, 42, 0.06);
|
||||
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.portfolio-overview__header {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.portfolio-overview__eyebrow {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.portfolio-overview--desktop .portfolio-overview__eyebrow {
|
||||
color: #6b7280;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.portfolio-overview__total {
|
||||
margin: 4px 0 0;
|
||||
font-size: 1.65rem;
|
||||
font-weight: 800;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.portfolio-overview__unit {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
margin-right: 4px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.portfolio-overview__profit {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 12px;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.portfolio-overview--desktop .portfolio-overview__profit {
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.portfolio-overview__profit--up {
|
||||
color: #bbf7d0;
|
||||
}
|
||||
|
||||
.portfolio-overview--desktop .portfolio-overview__profit--up {
|
||||
color: #027a48;
|
||||
background: #ecfdf3;
|
||||
}
|
||||
|
||||
.portfolio-overview__profit--down {
|
||||
color: #fecaca;
|
||||
}
|
||||
|
||||
.portfolio-overview--desktop .portfolio-overview__profit--down {
|
||||
color: #b42318;
|
||||
background: #fef3f2;
|
||||
}
|
||||
|
||||
.portfolio-overview__profit-percent {
|
||||
direction: ltr;
|
||||
unicode-bidi: isolate;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.portfolio-overview__profit-hint {
|
||||
width: 100%;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.portfolio-overview--desktop .portfolio-overview__profit-hint {
|
||||
color: #6b7280;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.portfolio-overview__grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.portfolio-overview__tile {
|
||||
padding: 10px 12px;
|
||||
border-radius: 10px;
|
||||
background: rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.portfolio-overview--desktop .portfolio-overview__tile {
|
||||
background: #f6f7f9;
|
||||
}
|
||||
|
||||
.portfolio-overview__tile-label {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
opacity: 0.85;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.portfolio-overview--desktop .portfolio-overview__tile-label {
|
||||
color: #6b7280;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.portfolio-overview__tile-value {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.portfolio-overview__tile-sub {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-size: 11px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.portfolio-overview--desktop .portfolio-overview__tile-sub {
|
||||
color: #9ca3af;
|
||||
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>
|
||||
@@ -32,7 +32,6 @@ export interface HomePageContext {
|
||||
homePageLoading: ReturnType<typeof useHomePageLoading>['homePageLoading']
|
||||
isGaroosi: ReturnType<typeof computed<boolean>>
|
||||
bannerEnabled: ReturnType<typeof computed<boolean>>
|
||||
limitedPrice: ReturnType<typeof computed>
|
||||
showClosedMessage: ReturnType<typeof computed<boolean>>
|
||||
closedMessage: ReturnType<typeof computed>
|
||||
mainActions: ReturnType<typeof computed<HomeActionItem[]>>
|
||||
@@ -89,7 +88,6 @@ export function useHomePage() {
|
||||
return custom || ''
|
||||
})
|
||||
const showClosedMessage = computed(() => !!marketClosedText.value)
|
||||
const limitedPrice = computed(() => walletStore.limitedPrice?.[0])
|
||||
const goldVal = computed(() => lastPrice.value?.price_last ?? 1)
|
||||
|
||||
const mainActions = computed<HomeActionItem[]>(() => [
|
||||
@@ -199,7 +197,7 @@ export function useHomePage() {
|
||||
const metalRial = resolveAssetsRialValue(summary, getTradeBuyPrice(lastPrice.value))
|
||||
Modal.confirm({
|
||||
title: 'دارایی ریالی',
|
||||
content: renderModalLines(formatRialAssetsInfoText(cash, metalRial)),
|
||||
content: renderModalLines(formatRialAssetsInfoText(cash, metalRial, summary?.portfolio)),
|
||||
okText: 'متوجه شدم',
|
||||
centered: true,
|
||||
cancelButtonProps: { style: { display: 'none' } },
|
||||
@@ -234,7 +232,6 @@ export function useHomePage() {
|
||||
homePageLoading,
|
||||
isGaroosi,
|
||||
bannerEnabled,
|
||||
limitedPrice,
|
||||
showClosedMessage,
|
||||
closedMessage: marketClosedText,
|
||||
mainActions,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { computed } from 'vue'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import BoursecuAssetsSummary from '../../components/BoursecuAssetsSummary.vue'
|
||||
import PortfolioOverview from '../../components/PortfolioOverview.vue'
|
||||
import { formatWeightAmount, usePriceWithoutType } from '@/utils/usePrice'
|
||||
import { useWalletAssets } from '@/composables/useWalletAssets'
|
||||
import type { WalletMetalTotal } from '@/models/walletAssets'
|
||||
@@ -24,6 +25,7 @@ const cashWallet = computed(() => assetsSummary.value?.cash_wallet ?? 0)
|
||||
const countBasedInventory = computed(() =>
|
||||
(assetsSummary.value?.count_products ?? []).reduce((sum, item) => sum + Number(item.quantity ?? 0), 0),
|
||||
)
|
||||
const showLegacyMetrics = computed(() => !assetsSummary.value?.portfolio)
|
||||
|
||||
type MetricItem = {
|
||||
id: string
|
||||
@@ -101,7 +103,10 @@ function handleInfo(metric: MetricItem) {
|
||||
icon-color="tertiary"
|
||||
/>
|
||||
|
||||
<div v-else class="dhome-metrics">
|
||||
<template v-else>
|
||||
<PortfolioOverview variant="desktop" />
|
||||
|
||||
<div v-if="showLegacyMetrics" class="dhome-metrics">
|
||||
<article v-for="metric in metrics" :key="metric.id" class="dhome-metric-tile">
|
||||
<div class="dhome-metric-tile__head">
|
||||
<span class="dhome-metric-tile__label">{{ metric.label }}</span>
|
||||
@@ -117,5 +122,6 @@ function handleInfo(metric: MetricItem) {
|
||||
</div>
|
||||
<p class="dhome-metric-tile__value">{{ metric.value }}</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -1,36 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, inject } from 'vue'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { usePriceWithoutType } from '@/utils/usePrice'
|
||||
import { inject } from 'vue'
|
||||
import { isBoursecuProject } from '@/utils/config'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import { HOME_PAGE_CONTEXT_KEY } from '../../composables/useHomePage'
|
||||
|
||||
const ctx = inject(HOME_PAGE_CONTEXT_KEY)!
|
||||
const userStore = useUserStore()
|
||||
const isBoursecu = isBoursecuProject()
|
||||
|
||||
const showLimitedPrice = computed(
|
||||
() =>
|
||||
ctx.limitedPrice.value !== undefined &&
|
||||
userStore.userInfo?.trade_active !== 1,
|
||||
)
|
||||
|
||||
const limitedPriceText = computed(() =>
|
||||
usePriceWithoutType(Number(ctx.limitedPrice.value?.value ?? 0)),
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="showLimitedPrice || ctx.showClosedMessage.value" class="dhome-alerts">
|
||||
<div v-if="showLimitedPrice" class="dhome-alert dhome-alert--warning">
|
||||
<BasicIcon icon="vuesax-linear:info-circle" size="14" />
|
||||
<span>
|
||||
حداقل مبلغ اولیه برای ثبت سفارش {{ limitedPriceText }} ریال میباشد.
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="!isBoursecu && ctx.showClosedMessage.value" class="dhome-alerts">
|
||||
<div
|
||||
v-if="!isBoursecu && ctx.showClosedMessage.value"
|
||||
class="dhome-alert dhome-alert--info"
|
||||
v-html="ctx.closedMessage.value"
|
||||
/>
|
||||
|
||||
@@ -37,17 +37,17 @@ const showSkeleton = computed(() => Boolean(getToken()) && !userInfo.value)
|
||||
<HomeSkeleton v-if="showSkeleton" />
|
||||
|
||||
<template v-if="showContent">
|
||||
<section class="dhome-hero-row">
|
||||
<DesktopLivePrice />
|
||||
<DesktopPriceRange />
|
||||
</section>
|
||||
|
||||
<DesktopAssetsMetrics
|
||||
v-if="!ctx.isGaroosi.value"
|
||||
@open-gold-modal="ctx.openGoldModal"
|
||||
@open-rial-modal="ctx.openRialModal"
|
||||
/>
|
||||
|
||||
<section class="dhome-hero-row">
|
||||
<DesktopLivePrice />
|
||||
<DesktopPriceRange />
|
||||
</section>
|
||||
|
||||
<div v-if="ctx.isGaroosi.value" class="dhome-default-grid">
|
||||
<div class="dhome-default-grid__main">
|
||||
<DesktopDefaultActions />
|
||||
|
||||
@@ -11,7 +11,6 @@ export const boursecuHomeLayoutConfig: HomeLayoutConfig = {
|
||||
{ id: 'assets' },
|
||||
{ id: 'currentPrice' },
|
||||
{ id: 'boursecuQuickActions' },
|
||||
{ id: 'limitedPriceAlert' },
|
||||
],
|
||||
sections: [
|
||||
{ id: 'marketClosed' },
|
||||
|
||||
@@ -4,12 +4,11 @@ import type { HomeLayoutConfig } from '../types'
|
||||
export const defaultHomeLayoutConfig: HomeLayoutConfig = {
|
||||
id: 'default',
|
||||
wrapperClass: 'p-1 from-primary rounded-lg p-px to-transparent mt-5',
|
||||
outerSections: [{ id: 'limitedPriceAlert' }],
|
||||
sections: [
|
||||
{ id: 'garoosiAssets' },
|
||||
{ id: 'assets' },
|
||||
{ id: 'mainActions' },
|
||||
{ id: 'referral' },
|
||||
{ id: 'assets' },
|
||||
{ id: 'marketClosed' },
|
||||
{ id: 'currentPrice' },
|
||||
{ id: 'banners' },
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, inject } from 'vue'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { usePriceWithoutType } from '@/utils/usePrice'
|
||||
import Icon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import { HOME_PAGE_CONTEXT_KEY } from '../../composables/useHomePage'
|
||||
|
||||
const ctx = inject(HOME_PAGE_CONTEXT_KEY)!
|
||||
const userStore = useUserStore()
|
||||
|
||||
const isVisible = computed(
|
||||
() =>
|
||||
ctx.limitedPrice.value !== undefined &&
|
||||
userStore.userInfo?.trade_active !== 1
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="isVisible"
|
||||
class="text-red text-xs flex gap-1 items-center justify-center mb-2"
|
||||
>
|
||||
<Icon icon="vuesax-linear:info-circle" size="16" />
|
||||
حداقل مبلغ اولیه برای ثبت سفارش
|
||||
{{ usePriceWithoutType(Number(ctx.limitedPrice.value?.value ?? 0)) }}
|
||||
ریال می باشد.
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { Component } from 'vue'
|
||||
import type { HomeSectionId } from '../types'
|
||||
import LimitedPriceAlertSection from './LimitedPriceAlertSection.vue'
|
||||
import GaroosiAssetsSection from './GaroosiAssetsSection.vue'
|
||||
import MainActionsSection from './MainActionsSection.vue'
|
||||
import BoursecuQuickActionsSection from './BoursecuQuickActionsSection.vue'
|
||||
@@ -13,7 +12,6 @@ import PriceChartSection from './PriceChartSection.vue'
|
||||
import PriceStatsSection from './PriceStatsSection.vue'
|
||||
|
||||
export const HOME_SECTION_COMPONENTS: Record<HomeSectionId, Component> = {
|
||||
limitedPriceAlert: LimitedPriceAlertSection,
|
||||
garoosiAssets: GaroosiAssetsSection,
|
||||
mainActions: MainActionsSection,
|
||||
boursecuQuickActions: BoursecuQuickActionsSection,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
export type HomeLayoutId = 'default' | 'boursecu'
|
||||
|
||||
export type HomeSectionId =
|
||||
| 'limitedPriceAlert'
|
||||
| 'garoosiAssets'
|
||||
| 'mainActions'
|
||||
| 'boursecuQuickActions'
|
||||
|
||||
@@ -54,11 +54,6 @@
|
||||
<div v-else class="w-full h-[88px]">
|
||||
<GoldSkeleton class="w-full" />
|
||||
</div>
|
||||
<div v-if="limitedPrice !== undefined && userStore?.userInfo.trade_active !== 1"
|
||||
class="text-red text-xs flex gap-1 items-center justify-center my-2">
|
||||
<Icon icon="vuesax-linear:info-circle" size="16" />
|
||||
حداقل مبلغ اولیه برای ثبت سفارش {{ usePriceWithoutType(limitedPrice.value as number ?? 0) }} ریال می باشد.
|
||||
</div>
|
||||
<!-- </a-spin>-->
|
||||
<a-form ref="refInput" :model="form" class="!mt-5" name="basic" @finish="okConfirm()">
|
||||
<div class="border-gray-200 border-1 rounded-xl p-5 relative">
|
||||
@@ -722,11 +717,6 @@ const unitCashCalculator = computed(() => {
|
||||
return cashWalletBalance.value / 100
|
||||
})
|
||||
|
||||
const limitedPrice = computed(() => {
|
||||
if (walletStore.limitedPrice)
|
||||
return walletStore?.limitedPrice[0]
|
||||
})
|
||||
|
||||
function handleGoldValueChange(val: string | number | undefined) {
|
||||
if (val === undefined || val === null || val === '') {
|
||||
form.value.goldValue = undefined
|
||||
|
||||
Reference in New Issue
Block a user