fix ذشزنلقخعدی
This commit is contained in:
@@ -276,6 +276,10 @@
|
|||||||
var tenantSecondary = normalizeHex(user.secondary_color)
|
var tenantSecondary = normalizeHex(user.secondary_color)
|
||||||
var tenantBackground = normalizeHex(user.background_color)
|
var tenantBackground = normalizeHex(user.background_color)
|
||||||
|
|
||||||
|
if (user.name === 'boursecu') {
|
||||||
|
tenantColor = '#2178b5'
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* مهم:
|
* مهم:
|
||||||
* background_color بکگراند اپ است.
|
* background_color بکگراند اپ است.
|
||||||
|
|||||||
+7
-6
@@ -99,7 +99,7 @@ import {LoadingOutlined} from '@ant-design/icons-vue'
|
|||||||
import img_bottomGradiant from '@/views/login/assets/img/gr_bottom_white.svg'
|
import img_bottomGradiant from '@/views/login/assets/img/gr_bottom_white.svg'
|
||||||
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||||
import {useConfigStore} from "@/store/configStore";
|
import {useConfigStore} from "@/store/configStore";
|
||||||
import {getProjectFontFamily, getProjectSplashBackground, getProjectSplashLogo, getProjectBrandRed, hasCustomLoginLogoLayout, isBoursecuProject, resetAppSurfaceBackground} from "@/utils/config";
|
import {getProjectFontFamily, getProjectSplashBackground, getProjectSplashLogo, getProjectBrandRed, getProjectPrimaryColor, hasCustomLoginLogoLayout, isBoursecuProject, resetAppSurfaceBackground} from "@/utils/config";
|
||||||
import {ConfigProvider as AConfigProvider} from 'ant-design-vue'
|
import {ConfigProvider as AConfigProvider} from 'ant-design-vue'
|
||||||
import BasicIcon from "@/components/BasicPacks/BasicIcon.vue";
|
import BasicIcon from "@/components/BasicPacks/BasicIcon.vue";
|
||||||
import { useApplyDesktopLayoutClass, useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
|
import { useApplyDesktopLayoutClass, useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
|
||||||
@@ -211,16 +211,17 @@ function handleSupportPointerUp(event: PointerEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const antTheme = computed(() => {
|
const antTheme = computed(() => {
|
||||||
if (!config.config?.color) return null
|
const primaryColor = getProjectPrimaryColor()
|
||||||
|
if (!primaryColor) return null
|
||||||
|
|
||||||
const brandRed = getProjectBrandRed()
|
const brandRed = getProjectBrandRed()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
token: {
|
token: {
|
||||||
colorPrimary: config.config.color,
|
colorPrimary: primaryColor,
|
||||||
colorLink: config.config.color,
|
colorLink: primaryColor,
|
||||||
colorLinkActive: config.config.color,
|
colorLinkActive: primaryColor,
|
||||||
colorLinkHover: config.config.color,
|
colorLinkHover: primaryColor,
|
||||||
fontFamily: getProjectFontFamily(),
|
fontFamily: getProjectFontFamily(),
|
||||||
...(brandRed ? {
|
...(brandRed ? {
|
||||||
colorError: brandRed,
|
colorError: brandRed,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { startPwaManifest } from '@/modules/pwa/manifest'
|
|||||||
import { createVueApp } from '@/app/createApp'
|
import { createVueApp } from '@/app/createApp'
|
||||||
import { setupRouterGuards } from '@/app/setupRouter'
|
import { setupRouterGuards } from '@/app/setupRouter'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import { applyProjectTheme } from '@/utils/config'
|
import { applyProjectTheme, isBoursecuProject } from '@/utils/config'
|
||||||
import { useSettingsStore } from '@/store/settingsStore'
|
import { useSettingsStore } from '@/store/settingsStore'
|
||||||
import { useUserStore } from '@/store/userStore'
|
import { useUserStore } from '@/store/userStore'
|
||||||
import { getToken } from '@/utils'
|
import { getToken } from '@/utils'
|
||||||
@@ -71,7 +71,7 @@ function applyTenantThemeVars() {
|
|||||||
const secondaryColor = window.__APP_CONFIG__?.user?.secondary_color
|
const secondaryColor = window.__APP_CONFIG__?.user?.secondary_color
|
||||||
const backgroundColor = window.__APP_CONFIG__?.user?.background_color
|
const backgroundColor = window.__APP_CONFIG__?.user?.background_color
|
||||||
|
|
||||||
if (color) {
|
if (color && !isBoursecuProject()) {
|
||||||
document.documentElement.style.setProperty('--App-primary-color', color)
|
document.documentElement.style.setProperty('--App-primary-color', color)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,17 @@
|
|||||||
html.project-boursecu {
|
html.project-boursecu {
|
||||||
|
.ant-btn-primary,
|
||||||
|
.ant-btn-primary:not(:disabled):hover,
|
||||||
|
.ant-btn-primary:not(:disabled):active {
|
||||||
|
background-color: var(--App-primary-color) !important;
|
||||||
|
border-color: var(--App-primary-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-radio-group-solid .ant-radio-button-wrapper-checked,
|
||||||
|
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover {
|
||||||
|
border-color: var(--App-primary-color) !important;
|
||||||
|
background-color: var(--App-primary-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-input-group-addon {
|
.ant-input-group-addon {
|
||||||
background-color: @secondary-color !important;
|
background-color: @secondary-color !important;
|
||||||
color: @App-primary-color !important;
|
color: @App-primary-color !important;
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 28 KiB |
@@ -1,6 +1,7 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { getProducts } from '@/api/products'
|
import { getProducts } from '@/api/products'
|
||||||
import type { Product } from '@/models/products'
|
import type { Product } from '@/models/products'
|
||||||
|
import { syncProductTradingHoursFromProducts } from '@/utils/storeHours'
|
||||||
|
|
||||||
/** Shared product catalog — dedupes getProducts() across wallet assets and chart offset. */
|
/** Shared product catalog — dedupes getProducts() across wallet assets and chart offset. */
|
||||||
const catalogProducts = ref<Product[]>([])
|
const catalogProducts = ref<Product[]>([])
|
||||||
@@ -22,6 +23,7 @@ export function getProductsCatalog(force = false): Promise<Product[]> {
|
|||||||
catalogPromise = getProducts()
|
catalogPromise = getProducts()
|
||||||
.then((products) => {
|
.then((products) => {
|
||||||
catalogProducts.value = products ?? []
|
catalogProducts.value = products ?? []
|
||||||
|
syncProductTradingHoursFromProducts(catalogProducts.value)
|
||||||
return catalogProducts.value
|
return catalogProducts.value
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
|
|||||||
@@ -20,7 +20,13 @@ const BOURSECU_PRIMARY_NAV: AppNavItem[] = [
|
|||||||
icon: 'vuesax-linear:receipt-item',
|
icon: 'vuesax-linear:receipt-item',
|
||||||
iconSrc: getBoursecuTransactionsIcon(),
|
iconSrc: getBoursecuTransactionsIcon(),
|
||||||
},
|
},
|
||||||
{ id: 'home', routeName: 'home', label: 'صفحه اصلی', icon: 'vuesax-linear:home' },
|
{
|
||||||
|
id: 'home',
|
||||||
|
routeName: 'home',
|
||||||
|
label: 'صفحه اصلی',
|
||||||
|
icon: 'vuesax-linear:home',
|
||||||
|
iconSrc: getBoursecuActionIconSrc('home'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'wallet',
|
id: 'wallet',
|
||||||
routeName: 'wallet',
|
routeName: 'wallet',
|
||||||
@@ -28,7 +34,13 @@ const BOURSECU_PRIMARY_NAV: AppNavItem[] = [
|
|||||||
icon: 'vuesax-linear:wallet-1',
|
icon: 'vuesax-linear:wallet-1',
|
||||||
iconSrc: getBoursecuActionIconSrc('wallet'),
|
iconSrc: getBoursecuActionIconSrc('wallet'),
|
||||||
},
|
},
|
||||||
{ id: 'user', routeName: 'user', label: 'تنظیمات', icon: 'vuesax-linear:setting-2' },
|
{
|
||||||
|
id: 'user',
|
||||||
|
routeName: 'user',
|
||||||
|
label: 'تنظیمات',
|
||||||
|
icon: 'vuesax-linear:setting-2',
|
||||||
|
iconSrc: getBoursecuActionIconSrc('settings'),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const DEFAULT_PRIMARY_NAV: AppNavItem[] = [
|
const DEFAULT_PRIMARY_NAV: AppNavItem[] = [
|
||||||
|
|||||||
@@ -52,7 +52,11 @@
|
|||||||
@click="$emit('navigate', 'home')"
|
@click="$emit('navigate', 'home')"
|
||||||
>
|
>
|
||||||
<div class="tab__icon">
|
<div class="tab__icon">
|
||||||
<Icon icon="vuesax-linear:home" size="24" />
|
<img
|
||||||
|
:src="boursecuHomeIcon"
|
||||||
|
alt=""
|
||||||
|
class="tab__icon-image"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs mt-1 font-bold">صفحه اصلی</div>
|
<div class="text-xs mt-1 font-bold">صفحه اصلی</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -101,7 +105,13 @@
|
|||||||
@click="$emit('navigate', userNavItem.routeName)"
|
@click="$emit('navigate', userNavItem.routeName)"
|
||||||
>
|
>
|
||||||
<div class="tab__icon">
|
<div class="tab__icon">
|
||||||
<Icon :icon="userNavItem.icon" size="24" />
|
<img
|
||||||
|
v-if="isBoursecu && userNavItem.routeName === 'user'"
|
||||||
|
:src="boursecuSettingsIcon"
|
||||||
|
alt=""
|
||||||
|
class="tab__icon-image"
|
||||||
|
/>
|
||||||
|
<Icon v-else :icon="userNavItem.icon" size="24" />
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs mt-1 font-bold">{{ userNavItem.label }}</div>
|
<div class="text-xs mt-1 font-bold">{{ userNavItem.label }}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -137,6 +147,8 @@ const userNavItem = computed(() =>
|
|||||||
const boursecuBuySellIcon = getBoursecuActionIconSrc('buySell') ?? ''
|
const boursecuBuySellIcon = getBoursecuActionIconSrc('buySell') ?? ''
|
||||||
const boursecuTransactionsIcon = getBoursecuActionIconSrc('transactions') ?? ''
|
const boursecuTransactionsIcon = getBoursecuActionIconSrc('transactions') ?? ''
|
||||||
const boursecuWalletIcon = getBoursecuActionIconSrc('wallet') ?? ''
|
const boursecuWalletIcon = getBoursecuActionIconSrc('wallet') ?? ''
|
||||||
|
const boursecuHomeIcon = getBoursecuActionIconSrc('home') ?? ''
|
||||||
|
const boursecuSettingsIcon = getBoursecuActionIconSrc('settings') ?? ''
|
||||||
const whiteNavLogoProjects = ['goldenramin']
|
const whiteNavLogoProjects = ['goldenramin']
|
||||||
const isWhiteNavLogo = computed(() => whiteNavLogoProjects.includes(projectName))
|
const isWhiteNavLogo = computed(() => whiteNavLogoProjects.includes(projectName))
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import boursecuConvertIcon from '@/assets/img/boursecu-convert-icon.png'
|
|||||||
import boursecuWalletIcon from '@/assets/img/boursecu-wallet-icon.png'
|
import boursecuWalletIcon from '@/assets/img/boursecu-wallet-icon.png'
|
||||||
import boursecuBuySellIcon from '@/assets/img/boursecu-buy-sell-icon.png'
|
import boursecuBuySellIcon from '@/assets/img/boursecu-buy-sell-icon.png'
|
||||||
import boursecuAssetsIcon from '@/assets/img/boursecu-assets-icon.png'
|
import boursecuAssetsIcon from '@/assets/img/boursecu-assets-icon.png'
|
||||||
|
import boursecuHomeIcon from '@/assets/img/boursecu-home-icon.png'
|
||||||
|
import boursecuSettingsIcon from '@/assets/img/boursecu-settings-icon.png'
|
||||||
import bazretalaLoginLogo from '@/assets/img/bazretala-login-logo.png'
|
import bazretalaLoginLogo from '@/assets/img/bazretala-login-logo.png'
|
||||||
import bazretalaNavLogo from '@/assets/img/bazretala-nav-logo.png'
|
import bazretalaNavLogo from '@/assets/img/bazretala-nav-logo.png'
|
||||||
import bazretalaFundBg from '@/assets/img/bazretala-fund-bg.png'
|
import bazretalaFundBg from '@/assets/img/bazretala-fund-bg.png'
|
||||||
@@ -53,6 +55,14 @@ export function getBoursecuAssetsIcon() {
|
|||||||
return boursecuAssetsIcon
|
return boursecuAssetsIcon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getBoursecuHomeIcon() {
|
||||||
|
return boursecuHomeIcon
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBoursecuSettingsIcon() {
|
||||||
|
return boursecuSettingsIcon
|
||||||
|
}
|
||||||
|
|
||||||
export function getBoursecuActionIconSrc(actionId: string): string | undefined {
|
export function getBoursecuActionIconSrc(actionId: string): string | undefined {
|
||||||
const icons: Record<string, string> = {
|
const icons: Record<string, string> = {
|
||||||
buySell: boursecuBuySellIcon,
|
buySell: boursecuBuySellIcon,
|
||||||
@@ -61,6 +71,8 @@ export function getBoursecuActionIconSrc(actionId: string): string | undefined {
|
|||||||
transactions: boursecuTransactionsIcon,
|
transactions: boursecuTransactionsIcon,
|
||||||
physicalReceive: boursecuPhysicalReceiveIcon,
|
physicalReceive: boursecuPhysicalReceiveIcon,
|
||||||
convert: boursecuConvertIcon,
|
convert: boursecuConvertIcon,
|
||||||
|
home: boursecuHomeIcon,
|
||||||
|
settings: boursecuSettingsIcon,
|
||||||
}
|
}
|
||||||
|
|
||||||
return icons[actionId]
|
return icons[actionId]
|
||||||
@@ -71,12 +83,26 @@ export function hasCustomLoginLogoLayout() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const BOURSECU_BRAND_RED = '#bc3c3b'
|
export const BOURSECU_BRAND_RED = '#bc3c3b'
|
||||||
|
export const BOURSECU_BRAND_BLUE = '#2178b5'
|
||||||
export const BOURSECU_BRAND_NAV = '#133050'
|
export const BOURSECU_BRAND_NAV = '#133050'
|
||||||
|
export const BOURSECU_COPPER_BG = '#b87333'
|
||||||
|
export const BOURSECU_COPPER_BG_LIGHT = '#fdf0e6'
|
||||||
|
export const BOURSECU_COPPER_BG_GRADIENT = 'linear-gradient(135deg, #c98a4a 0%, #a86528 100%)'
|
||||||
|
|
||||||
export function getProjectBrandRed() {
|
export function getProjectBrandRed() {
|
||||||
return isBoursecuProject() ? BOURSECU_BRAND_RED : null
|
return isBoursecuProject() ? BOURSECU_BRAND_RED : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** رنگ اصلی دکمهها و چارت — boursecu ثابت آبی، بقیه از تنظیمات tenant */
|
||||||
|
export function getProjectPrimaryColor() {
|
||||||
|
if (isBoursecuProject()) {
|
||||||
|
return BOURSECU_BRAND_BLUE
|
||||||
|
}
|
||||||
|
|
||||||
|
const color = getConfigApp('color')
|
||||||
|
return color != null ? String(color) : undefined
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveProjectColor(color?: string | null) {
|
export function resolveProjectColor(color?: string | null) {
|
||||||
if (!color || !isBoursecuProject()) {
|
if (!color || !isBoursecuProject()) {
|
||||||
return color
|
return color
|
||||||
@@ -240,6 +266,7 @@ export function applyProjectTheme() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.documentElement.classList.add('project-boursecu')
|
document.documentElement.classList.add('project-boursecu')
|
||||||
|
document.documentElement.style.setProperty('--App-primary-color', BOURSECU_BRAND_BLUE)
|
||||||
document.documentElement.style.setProperty('--App-brand-nav', BOURSECU_BRAND_NAV)
|
document.documentElement.style.setProperty('--App-brand-nav', BOURSECU_BRAND_NAV)
|
||||||
document.documentElement.style.setProperty('--App-brand-red', BOURSECU_BRAND_RED)
|
document.documentElement.style.setProperty('--App-brand-red', BOURSECU_BRAND_RED)
|
||||||
document.documentElement.style.setProperty('--App-brand-red-50', '#f8ecec')
|
document.documentElement.style.setProperty('--App-brand-red-50', '#f8ecec')
|
||||||
|
|||||||
@@ -394,10 +394,6 @@ export function formatMetalAssetsInfoText(metal: WalletMetalTotal): string {
|
|||||||
return `${metal.label}: مجموع کیف محصولات (${metal.product_weight} ${unit}) + موجودی کیف اصلی (${metal.melted_weight} ${unit})`
|
return `${metal.label}: مجموع کیف محصولات (${metal.product_weight} ${unit}) + موجودی کیف اصلی (${metal.melted_weight} ${unit})`
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatRialNumber(value: number): string {
|
|
||||||
return usePriceWithoutType(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** متن توضیح مودال دارایی ریالی */
|
/** متن توضیح مودال دارایی ریالی */
|
||||||
export function formatRialAssetsInfoText(cashWallet: number, metalRialValue: number): string {
|
export function formatRialAssetsInfoText(cashWallet: number, metalRialValue: number): string {
|
||||||
const total = cashWallet + metalRialValue
|
const total = cashWallet + metalRialValue
|
||||||
|
|||||||
@@ -86,7 +86,24 @@ export function isMeltedProductUsingAppPrice(product?: {
|
|||||||
group_type?: string | null
|
group_type?: string | null
|
||||||
reading_method?: string | null
|
reading_method?: string | null
|
||||||
direct_price?: number | null
|
direct_price?: number | null
|
||||||
|
trade_by_quantity?: boolean | number | null
|
||||||
|
price?: number | null
|
||||||
} | null) {
|
} | null) {
|
||||||
return isMeltedTradeGroup(product?.group_type)
|
if (!product || resolveMeltedPriceSource(product) !== 'app') {
|
||||||
&& resolveMeltedPriceSource(product) === 'app'
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMeltedTradeGroup(product.group_type)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!usesQuantityTrade(product)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Number(product.direct_price) === 1) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number(product.price ?? 0) <= 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,19 @@ export function isKatodMesGroup(groupType?: string | null) {
|
|||||||
return normalizeProductGroupType(groupType) === BOURSECU_GROUP_KATOD
|
return normalizeProductGroupType(groupType) === BOURSECU_GROUP_KATOD
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isShamshMesGroup(groupType?: string | null, title?: string | null) {
|
||||||
|
if (!isBoursecuProject()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const group = normalizeProductGroupType(groupType)
|
||||||
|
if (group === 'شمش') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return (title ?? '').includes('شمش')
|
||||||
|
}
|
||||||
|
|
||||||
export function isGoldOnlyDealGroup(groupType?: string | null) {
|
export function isGoldOnlyDealGroup(groupType?: string | null) {
|
||||||
return isMeltedTradeGroup(groupType)
|
return isMeltedTradeGroup(groupType)
|
||||||
}
|
}
|
||||||
@@ -51,6 +64,7 @@ export interface ProductUnitSource {
|
|||||||
direct_price?: number | null
|
direct_price?: number | null
|
||||||
group_type?: string | null
|
group_type?: string | null
|
||||||
amount?: number | null
|
amount?: number | null
|
||||||
|
trade_by_quantity?: boolean | number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isWeightBasedProductUnit(unit?: string | null) {
|
export function isWeightBasedProductUnit(unit?: string | null) {
|
||||||
@@ -111,8 +125,17 @@ export function formatProductWeightAmount(amount?: number | null, product?: Prod
|
|||||||
return `${value} ${weightUnit}`
|
return `${value} ${weightUnit}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** فقط محصول تعدادی (مثل سکه): «هر عدد X گرم» — معامله مقداری با trade_by_quantity نیست */
|
||||||
|
export function shouldShowPerUnitWeightHint(product?: ProductUnitSource | null) {
|
||||||
|
if (!product || !hasProductUnitWeight(product) || usesQuantityTrade(product)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return isCountBasedProduct(product)
|
||||||
|
}
|
||||||
|
|
||||||
export function getProductUnitWeightLabel(product?: ProductUnitSource | null) {
|
export function getProductUnitWeightLabel(product?: ProductUnitSource | null) {
|
||||||
if (!hasProductUnitWeight(product)) {
|
if (!shouldShowPerUnitWeightHint(product)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,12 +175,16 @@ export function formatProductInventoryBalance(product?: (ProductUnitSource & { w
|
|||||||
return `${quantityPart} · هر عدد ${unitWeight}`
|
return `${quantityPart} · هر عدد ${unitWeight}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/** محصول تعدادی: موجودی wallet.gold = تعداد قلم، نه وزن */
|
/** محصول تعدادی: موجودی wallet.gold = تعداد قلم، نه وزن — معامله مقداری با trade_by_quantity نیست */
|
||||||
export function isCountBasedProduct(product?: ProductUnitSource | null) {
|
export function isCountBasedProduct(product?: ProductUnitSource | null) {
|
||||||
if (!product) {
|
if (!product) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (usesQuantityTrade(product)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if (isWeightBasedProductUnit(product.unit)) {
|
if (isWeightBasedProductUnit(product.unit)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -567,3 +594,23 @@ export function getMetalTypeTradeLabel() {
|
|||||||
export function getTradePriceUnitLabel() {
|
export function getTradePriceUnitLabel() {
|
||||||
return useSettingsStore().metalTypeLabel
|
return useSettingsStore().metalTypeLabel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** برچسب قیمت حین معامله — معامله مقداری: نام و واحد همان محصول */
|
||||||
|
export function getTransactionTradePriceLabel(product?: (ProductUnitSource & { title?: string | null }) | null) {
|
||||||
|
if (product && usesQuantityTrade(product)) {
|
||||||
|
const unit = getProductQuantityUnit(product)
|
||||||
|
const title = (product.title ?? '').trim()
|
||||||
|
return title ? `قیمت هر ${unit} ${title}` : `قیمت هر ${unit}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return `قیمت هر ${getWeightUnit()} ${getTradePriceUnitLabel()}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/** وزن کل زیر مقدار مبادلهشده — فقط محصول تعدادی (نه trade_by_quantity) */
|
||||||
|
export function shouldShowTransactionTotalWeightLine(product?: ProductUnitSource | null) {
|
||||||
|
if (!product || usesQuantityTrade(product) || !hasProductUnitWeight(product)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return isCountBasedProduct(product)
|
||||||
|
}
|
||||||
|
|||||||
+67
-10
@@ -1,3 +1,4 @@
|
|||||||
|
import { ref } from 'vue'
|
||||||
import { useSettingsStore } from '@/store/settingsStore'
|
import { useSettingsStore } from '@/store/settingsStore'
|
||||||
import { lastPrice } from '@/utils/price'
|
import { lastPrice } from '@/utils/price'
|
||||||
|
|
||||||
@@ -13,7 +14,13 @@ export interface EffectiveTradingHours {
|
|||||||
enforced: boolean
|
enforced: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
let productTradingHours: { start: string; end: string } | null = null
|
const productTradingHours = ref<{ start: string; end: string } | null>(null)
|
||||||
|
const productTradingHoursVersion = ref(0)
|
||||||
|
|
||||||
|
function normalizeTradingTimeSetting(value: unknown, fallback: string): string {
|
||||||
|
const raw = String(value ?? '').trim()
|
||||||
|
return raw || fallback
|
||||||
|
}
|
||||||
|
|
||||||
function parseTimeToSeconds(time: string): number {
|
function parseTimeToSeconds(time: string): number {
|
||||||
const parts = time.trim().split(':').map(Number)
|
const parts = time.trim().split(':').map(Number)
|
||||||
@@ -22,20 +29,56 @@ function parseTimeToSeconds(time: string): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function formatTradingTimeDisplay(time: string): string {
|
export function formatTradingTimeDisplay(time: string): string {
|
||||||
const parts = time.trim().split(':')
|
const normalized = normalizeTradingTimeSetting(time, '')
|
||||||
|
if (!normalized) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = normalized.split(':')
|
||||||
if (parts.length >= 3) {
|
if (parts.length >= 3) {
|
||||||
return `${parts[0]}:${parts[1]}:${parts[2]}`
|
return `${parts[0]}:${parts[1]}:${parts[2]}`
|
||||||
}
|
}
|
||||||
if (parts.length >= 2) {
|
if (parts.length >= 2) {
|
||||||
return `${parts[0]}:${parts[1]}:00`
|
return `${parts[0]}:${parts[1]}:00`
|
||||||
}
|
}
|
||||||
return time
|
return normalized
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasProductTradingWindow(start?: string | null, end?: string | null): boolean {
|
||||||
|
const open = formatTradingTimeDisplay(normalizeTradingTimeSetting(start, '00:00:00'))
|
||||||
|
const close = formatTradingTimeDisplay(normalizeTradingTimeSetting(end, '00:00:00'))
|
||||||
|
if (!open || !close) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return open !== close
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setProductTradingHours(start?: string | null, end?: string | null) {
|
export function setProductTradingHours(start?: string | null, end?: string | null) {
|
||||||
if (start && end) {
|
if (!hasProductTradingWindow(start, end)) {
|
||||||
productTradingHours = { start, end }
|
productTradingHours.value = null
|
||||||
|
productTradingHoursVersion.value += 1
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
productTradingHours.value = {
|
||||||
|
start: normalizeTradingTimeSetting(start, '00:00'),
|
||||||
|
end: normalizeTradingTimeSetting(end, '23:59'),
|
||||||
|
}
|
||||||
|
productTradingHoursVersion.value += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
export function syncProductTradingHoursFromProducts(
|
||||||
|
products: Array<{ auto_open_at?: string | null; auto_close_at?: string | null }>,
|
||||||
|
) {
|
||||||
|
for (const product of products) {
|
||||||
|
if (hasProductTradingWindow(product.auto_open_at, product.auto_close_at)) {
|
||||||
|
setProductTradingHours(product.auto_open_at, product.auto_close_at)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setProductTradingHours(null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getStoreTradingHours(): StoreTradingHours {
|
export function getStoreTradingHours(): StoreTradingHours {
|
||||||
@@ -47,13 +90,14 @@ export function getStoreTradingHours(): StoreTradingHours {
|
|||||||
String(closeRaw ?? 'false').trim().toLowerCase() === 'true'
|
String(closeRaw ?? 'false').trim().toLowerCase() === 'true'
|
||||||
|
|
||||||
return {
|
return {
|
||||||
start: String(settings.getSettingByKey('startDay') ?? '00:00'),
|
start: normalizeTradingTimeSetting(settings.getSettingByKey('startDay'), '00:00'),
|
||||||
end: String(settings.getSettingByKey('endDay') ?? '23:59'),
|
end: normalizeTradingTimeSetting(settings.getSettingByKey('endDay'), '23:59'),
|
||||||
closePriceEnabled,
|
closePriceEnabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEffectiveTradingHours(): EffectiveTradingHours {
|
export function getEffectiveTradingHours(): EffectiveTradingHours {
|
||||||
|
void productTradingHoursVersion.value
|
||||||
const settings = getStoreTradingHours()
|
const settings = getStoreTradingHours()
|
||||||
if (settings.closePriceEnabled) {
|
if (settings.closePriceEnabled) {
|
||||||
return {
|
return {
|
||||||
@@ -63,10 +107,10 @@ export function getEffectiveTradingHours(): EffectiveTradingHours {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (productTradingHours?.start && productTradingHours?.end) {
|
if (productTradingHours.value?.start && productTradingHours.value?.end) {
|
||||||
return {
|
return {
|
||||||
start: productTradingHours.start,
|
start: productTradingHours.value.start,
|
||||||
end: productTradingHours.end,
|
end: productTradingHours.value.end,
|
||||||
enforced: true,
|
enforced: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,10 +136,18 @@ export function isStoreClosedBySchedule(now = new Date()): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isStoreClosed(): boolean {
|
export function isStoreClosed(): boolean {
|
||||||
|
void productTradingHoursVersion.value
|
||||||
|
const { closePriceEnabled } = getStoreTradingHours()
|
||||||
|
|
||||||
|
if (closePriceEnabled) {
|
||||||
if (lastPrice.value?.closed === true) {
|
if (lastPrice.value?.closed === true) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return isStoreClosedBySchedule()
|
||||||
|
}
|
||||||
|
|
||||||
|
// وقتی close_price خاموش است، صفر بودن قیمت مرجع به معنی بسته بودن فروشگاه نیست.
|
||||||
return isStoreClosedBySchedule()
|
return isStoreClosedBySchedule()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,5 +155,10 @@ export function getStoreClosedMessage(): string {
|
|||||||
const { start, end } = getEffectiveTradingHours()
|
const { start, end } = getEffectiveTradingHours()
|
||||||
const open = formatTradingTimeDisplay(start)
|
const open = formatTradingTimeDisplay(start)
|
||||||
const close = formatTradingTimeDisplay(end)
|
const close = formatTradingTimeDisplay(end)
|
||||||
|
|
||||||
|
if (!open || !close) {
|
||||||
|
return 'فروشگاه در حال حاضر بسته است.'
|
||||||
|
}
|
||||||
|
|
||||||
return `فروشگاه در حال حاضر بسته است. ساعت کاری از ${open} تا ${close} میباشد.`
|
return `فروشگاه در حال حاضر بسته است. ساعت کاری از ${open} تا ${close} میباشد.`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<div class="font-bold">{{ item.quantity ?? '-' }}</div>
|
<div class="font-bold">{{ item.quantity ?? '-' }}</div>
|
||||||
{{ item.unit }}
|
{{ item.unit }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="item.unit_weight" class="text-xs text-gray-500">
|
<div v-if="item.unit_weight && item.display_type === 'count'" class="text-xs text-gray-500">
|
||||||
هر عدد {{ item.unit_weight }}
|
هر عدد {{ item.unit_weight }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
>
|
>
|
||||||
<BasicIcon icon="vuesax-linear:info-circle" size="18" :color="iconColor" />
|
<BasicIcon icon="vuesax-linear:info-circle" size="18" :color="iconColor" />
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="openRowId === row.id"
|
v-if="openRowId === row.id"
|
||||||
class="boursecu-assets__popup"
|
class="boursecu-assets__popup"
|
||||||
@@ -37,7 +38,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -86,6 +86,7 @@ onClickOutside(rootRef, () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.boursecu-assets__row {
|
.boursecu-assets__row {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -122,9 +123,12 @@ onClickOutside(rootRef, () => {
|
|||||||
.boursecu-assets__popup {
|
.boursecu-assets__popup {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc(100% + 8px);
|
top: calc(100% + 8px);
|
||||||
right: 0;
|
left: 50%;
|
||||||
|
right: auto;
|
||||||
|
transform: translateX(-50%);
|
||||||
z-index: 40;
|
z-index: 40;
|
||||||
min-width: min(18rem, 78vw);
|
width: min(18rem, calc(100vw - 2rem));
|
||||||
|
max-width: calc(100vw - 2rem);
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
@@ -177,9 +181,11 @@ onClickOutside(rootRef, () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.boursecu-assets__popup {
|
.boursecu-assets__popup {
|
||||||
right: auto;
|
|
||||||
left: 0;
|
left: 0;
|
||||||
min-width: 20rem;
|
right: auto;
|
||||||
|
transform: none;
|
||||||
|
width: min(20rem, calc(100% - 1rem));
|
||||||
|
max-width: 20rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,6 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full relative mt-2 border-gray-200 border-1 rounded-lg py-3 flex flex-col justify-center home_appear_fade_in">
|
<div
|
||||||
<div class="text-gray-600 text-[12px] ml-2 w-full text-center">قیمت خرید از {{ config.config.name_fa }}</div>
|
class="w-full relative mt-2 rounded-lg py-3 flex flex-col justify-center home_appear_fade_in transition-colors duration-300"
|
||||||
|
:class="containerClass"
|
||||||
|
>
|
||||||
|
<template v-if="isBoursecu && showStoreClosed">
|
||||||
|
<div class="boursecu-closed-box">
|
||||||
|
<div class="boursecu-closed-box__icon-wrap">
|
||||||
|
<Icon icon="vuesax-linear:lock" size="22" color="#ffffff" />
|
||||||
|
</div>
|
||||||
|
<div class="boursecu-closed-box__title">فروشگاه بسته است</div>
|
||||||
|
<div class="boursecu-closed-box__hint">{{ storeClosedHint }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<div class="price-label" :class="isBoursecu ? 'price-label--boursecu' : 'text-gray-600 text-[12px] ml-2 w-full text-center'">
|
||||||
|
{{ priceLabel }}
|
||||||
|
</div>
|
||||||
<div class="w-full items-center flex justify-center">
|
<div class="w-full items-center flex justify-center">
|
||||||
<div v-if="price" class="flex gap-1 text-xs items-center">
|
<div v-if="price" class="flex gap-1 text-xs items-center">
|
||||||
<div
|
<div
|
||||||
@@ -19,11 +35,13 @@
|
|||||||
<Vue3Odometer
|
<Vue3Odometer
|
||||||
:value="displayGoldVal"
|
:value="displayGoldVal"
|
||||||
class="o-text !text-xl font-bold"
|
class="o-text !text-xl font-bold"
|
||||||
|
:class="isBoursecu ? 'price-odometer--boursecu' : ''"
|
||||||
style="font-family: iran-sans; direction: ltr"
|
style="font-family: iran-sans; direction: ltr"
|
||||||
/>
|
/>
|
||||||
ریال
|
<span :class="isBoursecu ? 'price-unit--boursecu' : ''">ریال</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -31,29 +49,128 @@
|
|||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import Icon from '@/components/BasicPacks/BasicIcon.vue'
|
import Icon from '@/components/BasicPacks/BasicIcon.vue'
|
||||||
import { useConfigStore } from '@/store/configStore'
|
import { useConfigStore } from '@/store/configStore'
|
||||||
|
import { useUserStore } from '@/store/userStore'
|
||||||
|
import { isBoursecuProject } from '@/utils/config'
|
||||||
import { getTradeBuyPrice, lastPrice, percentPriceChanged, price } from '@/utils/price'
|
import { getTradeBuyPrice, lastPrice, percentPriceChanged, price } from '@/utils/price'
|
||||||
|
import { getStoreClosedMessage, isStoreClosed } from '@/utils/storeHours'
|
||||||
import Vue3Odometer from 'vue3-odometer'
|
import Vue3Odometer from 'vue3-odometer'
|
||||||
import 'odometer/themes/odometer-theme-default.css'
|
import 'odometer/themes/odometer-theme-default.css'
|
||||||
import { formatSignedPercentChange } from '@/utils/usePrice'
|
import { formatSignedPercentChange } from '@/utils/usePrice'
|
||||||
|
|
||||||
const config = useConfigStore()
|
const config = useConfigStore()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const isBoursecu = isBoursecuProject()
|
||||||
|
|
||||||
|
const priceLabel = computed(() =>
|
||||||
|
isBoursecu ? 'قیمت لحظهای کاتد مس' : `قیمت خرید از ${config.config.name_fa}`
|
||||||
|
)
|
||||||
|
|
||||||
|
const showStoreClosed = computed(() => {
|
||||||
|
if (isBoursecu) {
|
||||||
|
return isStoreClosed()
|
||||||
|
}
|
||||||
|
return lastPrice?.value?.goldir === 0 && getTradeBuyPrice(lastPrice.value) === 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const storeClosedHint = computed(() => {
|
||||||
|
const custom = userStore.getValueByKey('closed_message')
|
||||||
|
if (typeof custom === 'string' && custom.trim()) {
|
||||||
|
return custom.replace(/<[^>]*>/g, '').trim()
|
||||||
|
}
|
||||||
|
return getStoreClosedMessage()
|
||||||
|
})
|
||||||
|
|
||||||
|
const containerClass = computed(() => {
|
||||||
|
if (!isBoursecu) {
|
||||||
|
return 'border-gray-200 border-1'
|
||||||
|
}
|
||||||
|
if (showStoreClosed.value) {
|
||||||
|
return 'boursecu-price-box boursecu-price-box--closed'
|
||||||
|
}
|
||||||
|
return 'boursecu-price-box boursecu-price-box--open'
|
||||||
|
})
|
||||||
|
|
||||||
const percentChange = computed(() => {
|
const percentChange = computed(() => {
|
||||||
if (!getTradeBuyPrice(lastPrice.value)) return 0
|
if (!getTradeBuyPrice(lastPrice.value)) return 0
|
||||||
return percentPriceChanged()
|
return percentPriceChanged()
|
||||||
})
|
})
|
||||||
|
|
||||||
const isMarketClosed = computed(() => {
|
|
||||||
return lastPrice?.value?.goldir === 0 && getTradeBuyPrice(lastPrice.value) === 0
|
|
||||||
})
|
|
||||||
|
|
||||||
const displayGoldVal = computed(() => {
|
const displayGoldVal = computed(() => {
|
||||||
if (isMarketClosed.value) return 0
|
if (showStoreClosed.value) return 0
|
||||||
return getTradeBuyPrice(lastPrice.value)
|
return getTradeBuyPrice(lastPrice.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
void price
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.boursecu-price-box {
|
||||||
|
border: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.boursecu-price-box--open {
|
||||||
|
background: linear-gradient(135deg, #c98a4a 0%, #a86528 100%);
|
||||||
|
box-shadow: 0 4px 14px rgba(168, 101, 40, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.boursecu-price-box--closed {
|
||||||
|
background: linear-gradient(135deg, #4a5568 0%, #2d3748 100%);
|
||||||
|
box-shadow: 0 4px 14px rgba(45, 55, 72, 0.22);
|
||||||
|
min-height: 88px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-label--boursecu {
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-odometer--boursecu {
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-unit--boursecu {
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.boursecu-closed-box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.boursecu-closed-box__icon-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.12);
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.boursecu-closed-box__title {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.boursecu-closed-box__hint {
|
||||||
|
color: rgba(255, 255, 255, 0.78);
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.6;
|
||||||
|
max-width: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
.price-change-badge {
|
.price-change-badge {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -25,6 +25,14 @@
|
|||||||
>
|
>
|
||||||
مفتول مس
|
مفتول مس
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="chart-metal-segment__item"
|
||||||
|
:class="{ 'chart-metal-segment__item--active': selectedMetal === 'shamsh' }"
|
||||||
|
@click="emit('update:selectedMetal', 'shamsh')"
|
||||||
|
>
|
||||||
|
شمش مس
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<a-radio-group v-model:value="selectedValue" button-style="solid" class="chart-timeline-group">
|
<a-radio-group v-model:value="selectedValue" button-style="solid" class="chart-timeline-group">
|
||||||
<a-radio-button value="DAILY" @click="handleUpdate('DAILY')">24 ساعت اخیر</a-radio-button>
|
<a-radio-button value="DAILY" @click="handleUpdate('DAILY')">24 ساعت اخیر</a-radio-button>
|
||||||
@@ -50,7 +58,7 @@
|
|||||||
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
||||||
import { useConfigStore } from '@/store/configStore'
|
import { useConfigStore } from '@/store/configStore'
|
||||||
import { usePrice } from '@/utils/usePrice'
|
import { usePrice } from '@/utils/usePrice'
|
||||||
import { getProjectFontFamily } from '@/utils/config'
|
import { getProjectFontFamily, getProjectPrimaryColor } from '@/utils/config'
|
||||||
import {
|
import {
|
||||||
computeKatodMesVisualYBounds,
|
computeKatodMesVisualYBounds,
|
||||||
prepareKatodMesChartPoints,
|
prepareKatodMesChartPoints,
|
||||||
@@ -100,7 +108,7 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const config = useConfigStore()
|
const config = useConfigStore()
|
||||||
const settingsStore = useSettingsStore()
|
const settingsStore = useSettingsStore()
|
||||||
const { maftolChartRatio } = usePriceChart()
|
const { selectedMetalChartRatio } = usePriceChart()
|
||||||
const chartContainerRef = ref<HTMLElement | null>(null)
|
const chartContainerRef = ref<HTMLElement | null>(null)
|
||||||
const chartMounted = ref(false)
|
const chartMounted = ref(false)
|
||||||
const selectedValue = ref(props.chartSelection)
|
const selectedValue = ref(props.chartSelection)
|
||||||
@@ -126,7 +134,7 @@ const katodDayRange = computed(() => {
|
|||||||
price: price.value,
|
price: price.value,
|
||||||
metal: props.selectedMetal,
|
metal: props.selectedMetal,
|
||||||
isBoursecuLayout: true,
|
isBoursecuLayout: true,
|
||||||
maftolRatio: maftolChartRatio.value,
|
metalRatio: selectedMetalChartRatio.value,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
const displayChartSeries = computed(() => {
|
const displayChartSeries = computed(() => {
|
||||||
@@ -287,7 +295,7 @@ const chartOptions = computed(() => {
|
|||||||
}),
|
}),
|
||||||
style: { fontFamily },
|
style: { fontFamily },
|
||||||
},
|
},
|
||||||
colors: [config.config.color],
|
colors: [getProjectPrimaryColor() ?? config.config?.color ?? '#2178b5'],
|
||||||
fill: {
|
fill: {
|
||||||
type: 'gradient',
|
type: 'gradient',
|
||||||
gradient: {
|
gradient: {
|
||||||
@@ -324,8 +332,8 @@ const handleUpdate = (timeline: string) => {
|
|||||||
.chart-metal-segment__item {
|
.chart-metal-segment__item {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
min-width: 6.5rem;
|
min-width: 5.5rem;
|
||||||
padding: 0.4rem 0.75rem;
|
padding: 0.4rem 0.6rem;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ withDefaults(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const { selectedChartMetal, maftolChartRatio } = usePriceChart()
|
const { selectedChartMetal, selectedMetalChartRatio } = usePriceChart()
|
||||||
const isBoursecuLayout = computed(() => getHomeLayoutId() === 'boursecu')
|
const isBoursecuLayout = computed(() => getHomeLayoutId() === 'boursecu')
|
||||||
|
|
||||||
const rangePrices = computed(() =>
|
const rangePrices = computed(() =>
|
||||||
@@ -54,7 +54,7 @@ const rangePrices = computed(() =>
|
|||||||
price: price.value,
|
price: price.value,
|
||||||
metal: selectedChartMetal.value,
|
metal: selectedChartMetal.value,
|
||||||
isBoursecuLayout: isBoursecuLayout.value,
|
isBoursecuLayout: isBoursecuLayout.value,
|
||||||
maftolRatio: maftolChartRatio.value,
|
metalRatio: selectedMetalChartRatio.value,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import type { Product } from '@/models/products'
|
|||||||
import { getProductsCatalog, getProductsCatalogRef } from '@/composables/useProductsCatalog'
|
import { getProductsCatalog, getProductsCatalogRef } from '@/composables/useProductsCatalog'
|
||||||
import { getHomeLayoutId } from '../layouts/registry'
|
import { getHomeLayoutId } from '../layouts/registry'
|
||||||
import { getProductBuyPrice, resolvePriceOffsetRatio } from '@/utils/productPrice'
|
import { getProductBuyPrice, resolvePriceOffsetRatio } from '@/utils/productPrice'
|
||||||
import { isKatodMesGroup, isMaftolMesGroup } from '@/utils/productDealMethod'
|
import { isKatodMesGroup, isMaftolMesGroup, isShamshMesGroup } from '@/utils/productDealMethod'
|
||||||
import { getTradeBuyPrice, getTradeSellPrice, lastPrice } from '@/utils/price'
|
import { getTradeBuyPrice, getTradeSellPrice, lastPrice } from '@/utils/price'
|
||||||
|
|
||||||
export type ChartMetalType = 'katod' | 'maftol'
|
export type ChartMetalType = 'katod' | 'maftol' | 'shamsh'
|
||||||
|
|
||||||
type ChartPoint = [number, number]
|
type ChartPoint = [number, number]
|
||||||
|
|
||||||
@@ -25,6 +25,7 @@ function resolveChartProducts(products: Product[]) {
|
|||||||
return {
|
return {
|
||||||
katod: findKatodProduct(products),
|
katod: findKatodProduct(products),
|
||||||
maftol: findMaftolProduct(products),
|
maftol: findMaftolProduct(products),
|
||||||
|
shamsh: findShamshProduct(products),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,17 +35,38 @@ const maftolChartRatio = computed(() => {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// lastPrice را track کن تا بعد از polling نسبت بهروز شود
|
|
||||||
void lastPrice.value?.price_last
|
void lastPrice.value?.price_last
|
||||||
|
|
||||||
const { katod, maftol } = resolveChartProducts(catalogProducts.value)
|
const { katod, maftol } = resolveChartProducts(catalogProducts.value)
|
||||||
return deriveMaftolChartRatio(maftol, katod)
|
return deriveMetalChartRatio(maftol, katod)
|
||||||
|
})
|
||||||
|
|
||||||
|
/** نسبت شمش مس به کاتد */
|
||||||
|
const shamshChartRatio = computed(() => {
|
||||||
|
if (getHomeLayoutId() !== 'boursecu') {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
void lastPrice.value?.price_last
|
||||||
|
|
||||||
|
const { katod, shamsh } = resolveChartProducts(catalogProducts.value)
|
||||||
|
return deriveMetalChartRatio(shamsh, katod)
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectedMetalChartRatio = computed(() => {
|
||||||
|
if (selectedChartMetal.value === 'maftol') {
|
||||||
|
return maftolChartRatio.value
|
||||||
|
}
|
||||||
|
if (selectedChartMetal.value === 'shamsh') {
|
||||||
|
return shamshChartRatio.value
|
||||||
|
}
|
||||||
|
return 0
|
||||||
})
|
})
|
||||||
|
|
||||||
let chartFetchPromise: Promise<void> | null = null
|
let chartFetchPromise: Promise<void> | null = null
|
||||||
let chartInitialized = false
|
let chartInitialized = false
|
||||||
let maftolOffsetPromise: Promise<void> | null = null
|
let metalOffsetPromise: Promise<void> | null = null
|
||||||
let maftolOffsetLoaded = false
|
let metalOffsetLoaded = false
|
||||||
|
|
||||||
function findKatodProduct(products: Product[]) {
|
function findKatodProduct(products: Product[]) {
|
||||||
return (
|
return (
|
||||||
@@ -60,6 +82,12 @@ function findMaftolProduct(products: Product[]) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findShamshProduct(products: Product[]) {
|
||||||
|
return products.find((product) =>
|
||||||
|
isShamshMesGroup(product.group_type, product.title)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function applyChartRatio(basePrice: number, ratio: number) {
|
function applyChartRatio(basePrice: number, ratio: number) {
|
||||||
if (!ratio) {
|
if (!ratio) {
|
||||||
return basePrice
|
return basePrice
|
||||||
@@ -68,12 +96,12 @@ function applyChartRatio(basePrice: number, ratio: number) {
|
|||||||
return Math.round(basePrice + basePrice * ratio)
|
return Math.round(basePrice + basePrice * ratio)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** بالاترین/پایینترین امروز — برای boursecu با نسبت مفتول نسبت به کاتد */
|
/** بالاترین/پایینترین امروز — برای boursecu با نسبت مفتول/شمش نسبت به کاتد */
|
||||||
export function resolveMetalDayRangePrices(options: {
|
export function resolveMetalDayRangePrices(options: {
|
||||||
price: { up?: { buy?: number; sell?: number }; buy?: number; sell?: number; price_last?: number } | null | undefined
|
price: { up?: { buy?: number; sell?: number }; buy?: number; sell?: number; price_last?: number } | null | undefined
|
||||||
metal: ChartMetalType
|
metal: ChartMetalType
|
||||||
isBoursecuLayout: boolean
|
isBoursecuLayout: boolean
|
||||||
maftolRatio: number
|
metalRatio: number
|
||||||
}) {
|
}) {
|
||||||
const baseHigh = options.price?.up?.buy ?? getTradeBuyPrice(options.price as Parameters<typeof getTradeBuyPrice>[0])
|
const baseHigh = options.price?.up?.buy ?? getTradeBuyPrice(options.price as Parameters<typeof getTradeBuyPrice>[0])
|
||||||
const baseLow = options.price?.up?.sell ?? getTradeSellPrice(options.price as Parameters<typeof getTradeSellPrice>[0])
|
const baseLow = options.price?.up?.sell ?? getTradeSellPrice(options.price as Parameters<typeof getTradeSellPrice>[0])
|
||||||
@@ -83,37 +111,36 @@ export function resolveMetalDayRangePrices(options: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
high: applyChartRatio(baseHigh, options.maftolRatio),
|
high: applyChartRatio(baseHigh, options.metalRatio),
|
||||||
low: applyChartRatio(baseLow, options.maftolRatio),
|
low: applyChartRatio(baseLow, options.metalRatio),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function deriveMaftolChartRatio(maftolProduct?: Product, katodProduct?: Product) {
|
function deriveMetalChartRatio(metalProduct?: Product, katodProduct?: Product) {
|
||||||
const maftolOffset = resolvePriceOffsetRatio(maftolProduct?.price_offset_percent)
|
const metalOffset = resolvePriceOffsetRatio(metalProduct?.price_offset_percent)
|
||||||
if (maftolOffset) {
|
if (metalOffset) {
|
||||||
return maftolOffset
|
return metalOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!maftolProduct) {
|
if (!metalProduct) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
const liveBuy = getTradeBuyPrice(lastPrice.value)
|
const liveBuy = getTradeBuyPrice(lastPrice.value)
|
||||||
const katodLive = katodProduct ? getProductBuyPrice(katodProduct) : liveBuy
|
const katodLive = katodProduct ? getProductBuyPrice(katodProduct) : liveBuy
|
||||||
const maftolLive = getProductBuyPrice(maftolProduct)
|
const metalLive = getProductBuyPrice(metalProduct)
|
||||||
|
|
||||||
// وقتی فروشگاه بسته است قیمت لحظهای صفر میشود — از قیمت ثابت کاتالوگ استفاده کن
|
|
||||||
const katodStatic = Number(katodProduct?.price ?? 0) + Number(katodProduct?.price_profit ?? 0)
|
const katodStatic = Number(katodProduct?.price ?? 0) + Number(katodProduct?.price_profit ?? 0)
|
||||||
const maftolStatic = Number(maftolProduct.price ?? 0) + Number(maftolProduct.price_profit ?? 0)
|
const metalStatic = Number(metalProduct.price ?? 0) + Number(metalProduct.price_profit ?? 0)
|
||||||
|
|
||||||
const referencePrice = katodLive || liveBuy || katodStatic
|
const referencePrice = katodLive || liveBuy || katodStatic
|
||||||
const maftolPrice = maftolLive || maftolStatic
|
const derivedMetalPrice = metalLive || metalStatic
|
||||||
|
|
||||||
if (!referencePrice || !maftolPrice || referencePrice === maftolPrice) {
|
if (!referencePrice || !derivedMetalPrice || referencePrice === derivedMetalPrice) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return (maftolPrice - referencePrice) / referencePrice
|
return (derivedMetalPrice - referencePrice) / referencePrice
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeChartTimestamp(timestamp: number) {
|
function normalizeChartTimestamp(timestamp: number) {
|
||||||
@@ -147,18 +174,19 @@ function buildChartPoints(items: Array<{ timestamp: number; value: number }>): C
|
|||||||
}
|
}
|
||||||
|
|
||||||
function syncChartSeries() {
|
function syncChartSeries() {
|
||||||
|
const ratio = selectedMetalChartRatio.value
|
||||||
const data =
|
const data =
|
||||||
getHomeLayoutId() === 'boursecu' && selectedChartMetal.value === 'maftol'
|
getHomeLayoutId() === 'boursecu' && selectedChartMetal.value !== 'katod'
|
||||||
? rawChartPoints.value.map(
|
? rawChartPoints.value.map(
|
||||||
([timestamp, value]) =>
|
([timestamp, value]) =>
|
||||||
[timestamp, applyChartRatio(value, maftolChartRatio.value)] as ChartPoint
|
[timestamp, applyChartRatio(value, ratio)] as ChartPoint
|
||||||
)
|
)
|
||||||
: rawChartPoints.value
|
: rawChartPoints.value
|
||||||
|
|
||||||
priceChartSeries.value[0].data = data
|
priceChartSeries.value[0].data = data
|
||||||
}
|
}
|
||||||
|
|
||||||
watch([selectedChartMetal, maftolChartRatio], () => {
|
watch([selectedChartMetal, maftolChartRatio, shamshChartRatio], () => {
|
||||||
syncChartSeries()
|
syncChartSeries()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -192,34 +220,34 @@ async function fetchPrices(force = false) {
|
|||||||
return chartFetchPromise
|
return chartFetchPromise
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadMaftolOffset(force = false) {
|
async function loadMetalChartOffsets(force = false) {
|
||||||
if (getHomeLayoutId() !== 'boursecu') {
|
if (getHomeLayoutId() !== 'boursecu') {
|
||||||
syncChartSeries()
|
syncChartSeries()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!force && maftolOffsetLoaded && catalogProducts.value.length > 0) {
|
if (!force && metalOffsetLoaded && catalogProducts.value.length > 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!force && maftolOffsetPromise) {
|
if (!force && metalOffsetPromise) {
|
||||||
return maftolOffsetPromise
|
return metalOffsetPromise
|
||||||
}
|
}
|
||||||
|
|
||||||
maftolOffsetPromise = (async () => {
|
metalOffsetPromise = (async () => {
|
||||||
try {
|
try {
|
||||||
await getProductsCatalog(force)
|
await getProductsCatalog(force)
|
||||||
syncChartSeries()
|
syncChartSeries()
|
||||||
maftolOffsetLoaded = true
|
metalOffsetLoaded = true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading maftol chart ratio:', error)
|
console.error('Error loading boursecu chart metal ratios:', error)
|
||||||
syncChartSeries()
|
syncChartSeries()
|
||||||
} finally {
|
} finally {
|
||||||
maftolOffsetPromise = null
|
metalOffsetPromise = null
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
|
||||||
return maftolOffsetPromise
|
return metalOffsetPromise
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateChartData(timeline: string) {
|
function updateChartData(timeline: string) {
|
||||||
@@ -230,8 +258,8 @@ function updateChartData(timeline: string) {
|
|||||||
function setChartMetal(metal: ChartMetalType) {
|
function setChartMetal(metal: ChartMetalType) {
|
||||||
selectedChartMetal.value = metal
|
selectedChartMetal.value = metal
|
||||||
|
|
||||||
if (metal === 'maftol' && getHomeLayoutId() === 'boursecu' && !catalogProducts.value.length) {
|
if (metal !== 'katod' && getHomeLayoutId() === 'boursecu' && !catalogProducts.value.length) {
|
||||||
void loadMaftolOffset(true)
|
void loadMetalChartOffsets(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,9 +270,12 @@ export function usePriceChart() {
|
|||||||
chartLoading,
|
chartLoading,
|
||||||
selectedChartMetal,
|
selectedChartMetal,
|
||||||
maftolChartRatio,
|
maftolChartRatio,
|
||||||
|
shamshChartRatio,
|
||||||
|
selectedMetalChartRatio,
|
||||||
updateChartData,
|
updateChartData,
|
||||||
fetchPrices,
|
fetchPrices,
|
||||||
loadMaftolOffset,
|
loadMaftolOffset: loadMetalChartOffsets,
|
||||||
|
loadMetalChartOffsets,
|
||||||
setChartMetal,
|
setChartMetal,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,7 +285,7 @@ export function fetchHomeChartPrices(force = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function loadHomeMaftolOffset(force = false) {
|
export function loadHomeMaftolOffset(force = false) {
|
||||||
return loadMaftolOffset(force)
|
return loadMetalChartOffsets(force)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resetHomePriceChart() {
|
export function resetHomePriceChart() {
|
||||||
@@ -264,6 +295,6 @@ export function resetHomePriceChart() {
|
|||||||
selectedChartMetal.value = 'katod'
|
selectedChartMetal.value = 'katod'
|
||||||
chartInitialized = false
|
chartInitialized = false
|
||||||
chartFetchPromise = null
|
chartFetchPromise = null
|
||||||
maftolOffsetLoaded = false
|
metalOffsetLoaded = false
|
||||||
maftolOffsetPromise = null
|
metalOffsetPromise = null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
import { computed, inject } from 'vue'
|
import { computed, inject } from 'vue'
|
||||||
import { useUserStore } from '@/store/userStore'
|
import { useUserStore } from '@/store/userStore'
|
||||||
import { usePriceWithoutType } from '@/utils/usePrice'
|
import { usePriceWithoutType } from '@/utils/usePrice'
|
||||||
|
import { isBoursecuProject } from '@/utils/config'
|
||||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||||
import { HOME_PAGE_CONTEXT_KEY } from '../../composables/useHomePage'
|
import { HOME_PAGE_CONTEXT_KEY } from '../../composables/useHomePage'
|
||||||
|
|
||||||
const ctx = inject(HOME_PAGE_CONTEXT_KEY)!
|
const ctx = inject(HOME_PAGE_CONTEXT_KEY)!
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
const isBoursecu = isBoursecuProject()
|
||||||
|
|
||||||
const showLimitedPrice = computed(
|
const showLimitedPrice = computed(
|
||||||
() =>
|
() =>
|
||||||
@@ -28,7 +30,7 @@ const limitedPriceText = computed(() =>
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="ctx.showClosedMessage.value"
|
v-if="!isBoursecu && ctx.showClosedMessage.value"
|
||||||
class="dhome-alert dhome-alert--info"
|
class="dhome-alert dhome-alert--info"
|
||||||
v-html="ctx.closedMessage.value"
|
v-html="ctx.closedMessage.value"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,7 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dhome-surface dhome-hero-price">
|
<div
|
||||||
<span class="dhome-label">قیمت لحظهای</span>
|
class="dhome-surface dhome-hero-price"
|
||||||
<p class="dhome-hero-price__brand">{{ config.config?.name_fa }}</p>
|
:class="{
|
||||||
|
'dhome-hero-price--boursecu-open': isBoursecu && !showStoreClosed,
|
||||||
|
'dhome-hero-price--boursecu-closed': isBoursecu && showStoreClosed,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template v-if="isBoursecu && showStoreClosed">
|
||||||
|
<div class="dhome-hero-price__closed">
|
||||||
|
<div class="dhome-hero-price__closed-icon">
|
||||||
|
<BasicIcon icon="vuesax-linear:lock" size="22" color="#ffffff" />
|
||||||
|
</div>
|
||||||
|
<p class="dhome-hero-price__closed-title">فروشگاه بسته است</p>
|
||||||
|
<p class="dhome-hero-price__closed-hint">{{ storeClosedHint }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<span class="dhome-label" :class="{ 'dhome-label--boursecu': isBoursecu }">
|
||||||
|
{{ isBoursecu ? 'قیمت لحظهای کاتد مس' : 'قیمت لحظهای' }}
|
||||||
|
</span>
|
||||||
|
<p v-if="!isBoursecu" class="dhome-hero-price__brand">{{ config.config?.name_fa }}</p>
|
||||||
<div class="dhome-hero-price__value-row">
|
<div class="dhome-hero-price__value-row">
|
||||||
<span
|
<span
|
||||||
v-if="percentChange !== 0"
|
v-if="percentChange !== 0"
|
||||||
@@ -17,10 +36,12 @@
|
|||||||
<Vue3Odometer
|
<Vue3Odometer
|
||||||
:value="displayGoldVal"
|
:value="displayGoldVal"
|
||||||
class="dhome-hero-price__odometer o-text"
|
class="dhome-hero-price__odometer o-text"
|
||||||
|
:class="{ 'dhome-hero-price__odometer--boursecu': isBoursecu }"
|
||||||
style="font-family: iran-sans"
|
style="font-family: iran-sans"
|
||||||
/>
|
/>
|
||||||
<span class="dhome-hero-price__unit">ریال</span>
|
<span class="dhome-hero-price__unit" :class="{ 'dhome-hero-price__unit--boursecu': isBoursecu }">ریال</span>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -30,17 +51,33 @@ import Vue3Odometer from 'vue3-odometer'
|
|||||||
import 'odometer/themes/odometer-theme-default.css'
|
import 'odometer/themes/odometer-theme-default.css'
|
||||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||||
import { useConfigStore } from '@/store/configStore'
|
import { useConfigStore } from '@/store/configStore'
|
||||||
|
import { useUserStore } from '@/store/userStore'
|
||||||
|
import { isBoursecuProject } from '@/utils/config'
|
||||||
import { getTradeBuyPrice, lastPrice, percentPriceChanged } from '@/utils/price'
|
import { getTradeBuyPrice, lastPrice, percentPriceChanged } from '@/utils/price'
|
||||||
|
import { getStoreClosedMessage, isStoreClosed } from '@/utils/storeHours'
|
||||||
import { formatSignedPercentChange } from '@/utils/usePrice'
|
import { formatSignedPercentChange } from '@/utils/usePrice'
|
||||||
|
|
||||||
const config = useConfigStore()
|
const config = useConfigStore()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const isBoursecu = isBoursecuProject()
|
||||||
|
|
||||||
const isMarketClosed = computed(
|
const showStoreClosed = computed(() => {
|
||||||
() => lastPrice?.value?.goldir === 0 && getTradeBuyPrice(lastPrice.value) === 0,
|
if (isBoursecu) {
|
||||||
)
|
return isStoreClosed()
|
||||||
|
}
|
||||||
|
return lastPrice?.value?.goldir === 0 && getTradeBuyPrice(lastPrice.value) === 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const storeClosedHint = computed(() => {
|
||||||
|
const custom = userStore.getValueByKey('closed_message')
|
||||||
|
if (typeof custom === 'string' && custom.trim()) {
|
||||||
|
return custom.replace(/<[^>]*>/g, '').trim()
|
||||||
|
}
|
||||||
|
return getStoreClosedMessage()
|
||||||
|
})
|
||||||
|
|
||||||
const displayGoldVal = computed(() => {
|
const displayGoldVal = computed(() => {
|
||||||
if (isMarketClosed.value) return 0
|
if (showStoreClosed.value) return 0
|
||||||
return getTradeBuyPrice(lastPrice.value)
|
return getTradeBuyPrice(lastPrice.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -49,3 +86,61 @@ const percentChange = computed(() => {
|
|||||||
return percentPriceChanged()
|
return percentPriceChanged()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.dhome-hero-price--boursecu-open {
|
||||||
|
background: linear-gradient(135deg, #c98a4a 0%, #a86528 100%);
|
||||||
|
border-color: transparent;
|
||||||
|
box-shadow: 0 4px 14px rgba(168, 101, 40, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dhome-hero-price--boursecu-closed {
|
||||||
|
background: linear-gradient(135deg, #4a5568 0%, #2d3748 100%);
|
||||||
|
border-color: transparent;
|
||||||
|
box-shadow: 0 4px 14px rgba(45, 55, 72, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dhome-label--boursecu {
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dhome-hero-price__odometer--boursecu {
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dhome-hero-price__unit--boursecu {
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dhome-hero-price__closed {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dhome-hero-price__closed-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.12);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dhome-hero-price__closed-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dhome-hero-price__closed-hint {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.7;
|
||||||
|
color: rgba(255, 255, 255, 0.78);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { usePriceWithoutType } from '@/utils/usePrice'
|
|||||||
import { getHomeLayoutId } from '../../layouts/registry'
|
import { getHomeLayoutId } from '../../layouts/registry'
|
||||||
import { resolveMetalDayRangePrices, usePriceChart } from '../../composables/usePriceChart'
|
import { resolveMetalDayRangePrices, usePriceChart } from '../../composables/usePriceChart'
|
||||||
|
|
||||||
const { selectedChartMetal, maftolChartRatio } = usePriceChart()
|
const { selectedChartMetal, selectedMetalChartRatio } = usePriceChart()
|
||||||
const isBoursecuLayout = computed(() => getHomeLayoutId() === 'boursecu')
|
const isBoursecuLayout = computed(() => getHomeLayoutId() === 'boursecu')
|
||||||
|
|
||||||
const rangePrices = computed(() =>
|
const rangePrices = computed(() =>
|
||||||
@@ -13,7 +13,7 @@ const rangePrices = computed(() =>
|
|||||||
price: price.value,
|
price: price.value,
|
||||||
metal: selectedChartMetal.value,
|
metal: selectedChartMetal.value,
|
||||||
isBoursecuLayout: isBoursecuLayout.value,
|
isBoursecuLayout: isBoursecuLayout.value,
|
||||||
maftolRatio: maftolChartRatio.value,
|
metalRatio: selectedMetalChartRatio.value,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { inject } from 'vue'
|
import { inject } from 'vue'
|
||||||
|
import { isBoursecuProject } from '@/utils/config'
|
||||||
import MarketClosedMessage from '../../components/MarketClosedMessage.vue'
|
import MarketClosedMessage from '../../components/MarketClosedMessage.vue'
|
||||||
import { HOME_PAGE_CONTEXT_KEY } from '../../composables/useHomePage'
|
import { HOME_PAGE_CONTEXT_KEY } from '../../composables/useHomePage'
|
||||||
|
|
||||||
@@ -8,7 +9,7 @@ const ctx = inject(HOME_PAGE_CONTEXT_KEY)!
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MarketClosedMessage
|
<MarketClosedMessage
|
||||||
v-if="ctx.showClosedMessage.value"
|
v-if="!isBoursecuProject() && ctx.showClosedMessage.value"
|
||||||
:message="ctx.closedMessage.value as string"
|
:message="ctx.closedMessage.value as string"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -104,7 +104,7 @@
|
|||||||
<div>{{ totalWeightDisplay }}</div>
|
<div>{{ totalWeightDisplay }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="isCountBasedProductItem && hasProductUnitWeight(props?.ProductItem)"
|
v-if="shouldShowPerUnitWeightHint(props?.ProductItem)"
|
||||||
class="flex justify-between text-xs text-gray-500"
|
class="flex justify-between text-xs text-gray-500"
|
||||||
>
|
>
|
||||||
<div>وزن هر عدد</div>
|
<div>وزن هر عدد</div>
|
||||||
@@ -284,6 +284,7 @@ import type { Transaction } from '@/models/transaction'
|
|||||||
import type { TableEngineInput, TableOutput } from '@/models/table'
|
import type { TableEngineInput, TableOutput } from '@/models/table'
|
||||||
import { getTransactionTable } from '@/api/transaction'
|
import { getTransactionTable } from '@/api/transaction'
|
||||||
import { getLastPrices, getTradeBuyPrice, getTradeSellPrice, lastPrice, setIntervalGetAllPrice, stopPriceInterval } from '@/utils/price'
|
import { getLastPrices, getTradeBuyPrice, getTradeSellPrice, lastPrice, setIntervalGetAllPrice, stopPriceInterval } from '@/utils/price'
|
||||||
|
import { getProductBuyPrice, getProductSellPrice, isProductPriceInactive } from '@/utils/productPrice'
|
||||||
import { Product } from '@/models/products'
|
import { Product } from '@/models/products'
|
||||||
import BasicSelect from '@/components/BasicPacks/BasicSelect.vue'
|
import BasicSelect from '@/components/BasicPacks/BasicSelect.vue'
|
||||||
import Bg_main from '@/components/bg_main.vue'
|
import Bg_main from '@/components/bg_main.vue'
|
||||||
@@ -311,6 +312,7 @@ import {
|
|||||||
getProductUnitWeightLabel,
|
getProductUnitWeightLabel,
|
||||||
hasProductUnitWeight,
|
hasProductUnitWeight,
|
||||||
isCountBasedProduct,
|
isCountBasedProduct,
|
||||||
|
shouldShowPerUnitWeightHint,
|
||||||
shouldShowProductTotalWeight,
|
shouldShowProductTotalWeight,
|
||||||
} from '@/utils/productDealMethod'
|
} from '@/utils/productDealMethod'
|
||||||
import {
|
import {
|
||||||
@@ -468,8 +470,11 @@ const disableTransaction = computed(() =>
|
|||||||
: (form.value?.goldValue ?? 0) < 0.001)) ||
|
: (form.value?.goldValue ?? 0) < 0.001)) ||
|
||||||
(maxOrderCash.value != null && (form.value?.goldCash ?? 0) > maxOrderCash.value) ||
|
(maxOrderCash.value != null && (form.value?.goldCash ?? 0) > maxOrderCash.value) ||
|
||||||
cashValidationMessage.value !== '' ||
|
cashValidationMessage.value !== '' ||
|
||||||
!lastPrice.value ||
|
!props.ProductItem ||
|
||||||
(isBuy.value ? getTradeBuyPrice(lastPrice.value) <= 0 : getTradeSellPrice(lastPrice.value) <= 0))
|
isProductPriceInactive(props.ProductItem) ||
|
||||||
|
(isBuy.value
|
||||||
|
? getProductBuyPrice(props.ProductItem) <= 0
|
||||||
|
: getProductSellPrice(props.ProductItem) <= 0))
|
||||||
|
|
||||||
function refreshTable() {
|
function refreshTable() {
|
||||||
sellItems.value = []
|
sellItems.value = []
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex bg-white border-1 border-gray-200 rounded-lg"
|
<div class="flex bg-white border-1 border-gray-200 rounded-lg"
|
||||||
:class="isClickable ? 'bg-white opacity-100' : 'bg-gray-200 opacity-50 cursor-not-allowed'">
|
:class="isClickable ? 'bg-white opacity-100' : 'bg-gray-200 opacity-50 cursor-not-allowed'">
|
||||||
<div class="bg-gray-100 m-2 rounded-lg w-28 h-28 flex justify-center p-2">
|
<div class="bg-gray-100 m-1.5 rounded-lg w-24 h-24 flex justify-center p-1.5 shrink-0">
|
||||||
<img :src="displayImage" class="object-contain w-full h-full" alt="">
|
<img :src="displayImage" class="object-contain w-full h-full" alt="">
|
||||||
</div>
|
</div>
|
||||||
<div class="m-1 flex flex-col gap-0 justify-center leading-tight">
|
<div class="m-1 flex flex-col gap-0 justify-center leading-tight">
|
||||||
<div class="text-base font-500">{{ props.ProductItem.title }}</div>
|
<div class="text-base font-500">{{ props.ProductItem.title }}</div>
|
||||||
<div class="text-base font-500">{{ usePrice(getProductBuyPrice(props.ProductItem)) }}</div>
|
<div class="text-base font-500">
|
||||||
|
<template v-if="props.inactive">بدون قیمت</template>
|
||||||
|
<template v-else>{{ usePrice(getProductBuyPrice(props.ProductItem)) }}</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="absolute inset-0 bg-cover bg-center opacity-60"></div>
|
<div class="absolute inset-0 bg-cover bg-center opacity-60 pointer-events-none"></div>
|
||||||
<MobilePageHeader title="تراکنش ها">
|
<MobilePageHeader title="تراکنش ها">
|
||||||
<template #trailing>
|
<template #trailing>
|
||||||
<BasicIcon icon="vuesax-linear:filter" @click="showFilter = true" />
|
<BasicIcon icon="vuesax-linear:filter" @click="showFilter = true" />
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
</MobilePageHeader>
|
</MobilePageHeader>
|
||||||
|
|
||||||
<!-- <div ref="el" :class="loading ? 'h-screen' : ''" class="px-3 mpage-content pb-10">-->
|
<!-- <div ref="el" :class="loading ? 'h-screen' : ''" class="px-3 mpage-content pb-10">-->
|
||||||
<div ref="el" class="transactions-page-body px-3 pt-16">
|
<div ref="el" class="transactions-page-body px-3 pt-16 pb-24">
|
||||||
<list-anim v-if="!loading">
|
<list-anim v-if="!loading">
|
||||||
<Item v-for="item in items" :key="item.id" :item="item" />
|
<Item v-for="item in items" :key="item.id" :item="item" />
|
||||||
<div :ref="bindMobileObserver" v-show="!loading" class="w-full opacity-0 relative">
|
<div :ref="bindMobileObserver" v-show="!loading" class="w-full opacity-0 relative">
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="item.order" :class="variant === 'desktop' ? 'dtxn-detail__row' : 'flex justify-between mb-3'">
|
<div v-if="item.order" :class="variant === 'desktop' ? 'dtxn-detail__row' : 'flex justify-between mb-3'">
|
||||||
<div :class="variant === 'desktop' ? 'dtxn-detail__label' : 'text-primary'">
|
<div :class="variant === 'desktop' ? 'dtxn-detail__label' : 'text-primary'">
|
||||||
قیمت هر گرم {{ getTradePriceUnitLabel() }} حین معامله:
|
{{ getTransactionTradePriceLabel(item.wallet?.product) }} حین معامله:
|
||||||
</div>
|
</div>
|
||||||
<div :class="['flex text-xs items-center number-ltr', variant === 'desktop' ? 'dtxn-detail__value' : '']">
|
<div :class="['flex text-xs items-center number-ltr', variant === 'desktop' ? 'dtxn-detail__value' : '']">
|
||||||
<div class="text-base ml-1">{{ usePriceWithoutType(item.order?.price ?? 0) }}</div>
|
<div class="text-base ml-1">{{ usePriceWithoutType(item.order?.price ?? 0) }}</div>
|
||||||
@@ -38,10 +38,10 @@
|
|||||||
{{ item.unit }}
|
{{ item.unit }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="item.wallet?.product && getProductTotalWeightLabel(Math.abs(item.gold ?? 0), item.wallet.product)"
|
v-if="shouldShowTransactionTotalWeightLine(item.wallet?.product)"
|
||||||
class="text-gray-500"
|
class="text-gray-500"
|
||||||
>
|
>
|
||||||
{{ getProductTotalWeightLabel(Math.abs(item.gold ?? 0), item.wallet.product) }}
|
{{ getProductTotalWeightLabel(Math.abs(item.gold ?? 0), item.wallet?.product) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -116,8 +116,9 @@ import {
|
|||||||
getProductQuantityUnit,
|
getProductQuantityUnit,
|
||||||
getProductTotalWeightLabel,
|
getProductTotalWeightLabel,
|
||||||
getProductUnitWeightLabel,
|
getProductUnitWeightLabel,
|
||||||
getTradePriceUnitLabel,
|
getTransactionTradePriceLabel,
|
||||||
getWeightUnit,
|
getWeightUnit,
|
||||||
|
shouldShowTransactionTotalWeightLine,
|
||||||
} from '@/utils/productDealMethod'
|
} from '@/utils/productDealMethod'
|
||||||
import { toJalaliNotime, toTime } from '@/utils/useDateTime'
|
import { toJalaliNotime, toTime } from '@/utils/useDateTime'
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user