Merge feature/responsive-layout-refactor into modular for desktop layouts.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+14
-1
@@ -114,7 +114,7 @@ export function getProjectLoginTaglineLines() {
|
||||
}
|
||||
|
||||
return [
|
||||
'بورس مس مرجع هوشمند معاملات',
|
||||
'مرجع هوشمند معاملات',
|
||||
' و سرمایهگذاری در صنعت مس',
|
||||
]
|
||||
}
|
||||
@@ -123,6 +123,18 @@ export function getProjectSplashBackground() {
|
||||
return isBoursecuProject() ? BOURSECU_BRAND_NAV : '#ffffff'
|
||||
}
|
||||
|
||||
/** Reset splash-time inline backgrounds so mobile overscroll shows white again. */
|
||||
export function resetAppSurfaceBackground() {
|
||||
const background = '#ffffff'
|
||||
document.documentElement.style.backgroundColor = background
|
||||
document.body.style.backgroundColor = background
|
||||
|
||||
const metaTheme = document.querySelector('meta[name="theme-color"]')
|
||||
if (metaTheme) {
|
||||
metaTheme.setAttribute('content', background)
|
||||
}
|
||||
}
|
||||
|
||||
export function getProjectFontFamily() {
|
||||
return getConfigApp('name') === 'boursecu' ? "'dana', sans-serif" : "'iran-sans', sans-serif"
|
||||
}
|
||||
@@ -142,6 +154,7 @@ export function applyProjectTheme() {
|
||||
}
|
||||
|
||||
document.documentElement.classList.add('project-boursecu')
|
||||
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-50', '#f8ecec')
|
||||
document.documentElement.style.setProperty('--App-brand-red-100', '#f0d8d8')
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
const PERSIAN_DIGITS = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'] as const
|
||||
|
||||
export function toPersianDigits(value: string | number | null | undefined): string {
|
||||
if (value === null || value === undefined) return ''
|
||||
return String(value).replace(/\d/g, (digit) => PERSIAN_DIGITS[Number(digit)] ?? digit)
|
||||
}
|
||||
|
||||
export function toEnglishDigits(value: string): string {
|
||||
return value.replace(/[۰-۹]/g, (digit) => {
|
||||
const index = PERSIAN_DIGITS.indexOf(digit as (typeof PERSIAN_DIGITS)[number])
|
||||
return index >= 0 ? String(index) : digit
|
||||
})
|
||||
}
|
||||
|
||||
export function formatFixed(value: string | number | null | undefined, decimals = 2): string {
|
||||
const num = Number(value)
|
||||
if (!Number.isFinite(num)) return toPersianDigits(String(value ?? ''))
|
||||
return toPersianDigits(num.toFixed(decimals))
|
||||
}
|
||||
|
||||
export function formatPercent(
|
||||
value: number,
|
||||
decimals = 2,
|
||||
options: { showSign?: boolean; suffix?: string } = {},
|
||||
): string {
|
||||
const { showSign = false, suffix = '%' } = options
|
||||
const prefix = showSign && value > 0 ? '+' : value < 0 ? '-' : showSign ? '+' : ''
|
||||
return `${prefix}${formatFixed(Math.abs(value), decimals)}${suffix}`
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { toPersianDigits } from '@/utils/persianDigits'
|
||||
import type { Product } from '@/models/products'
|
||||
import type { UserWalletLine } from '@/models/wallet'
|
||||
import type { ProductFile } from '@/utils/file'
|
||||
@@ -46,6 +47,138 @@ export function getTotalMetalLabel(metalId: number): string {
|
||||
return `کل دارایی ${getMetalLabel(metalId)}`
|
||||
}
|
||||
|
||||
/** boursecu home — ترتیب نمایش: مس، طلا، سایر فلزها */
|
||||
export function resolveBoursecuAssetMetalTotals(
|
||||
metalTotals: WalletMetalTotal[]
|
||||
): WalletMetalTotal[] {
|
||||
if (!metalTotals.length) {
|
||||
return []
|
||||
}
|
||||
|
||||
const ordered: WalletMetalTotal[] = []
|
||||
const copper = metalTotals.find((metal) => metal.metal_id === METAL_COPPER)
|
||||
const gold = metalTotals.find((metal) => metal.metal_id === METAL_GOLD)
|
||||
|
||||
if (copper) {
|
||||
ordered.push(copper)
|
||||
}
|
||||
if (gold) {
|
||||
ordered.push(gold)
|
||||
}
|
||||
|
||||
for (const metal of metalTotals) {
|
||||
if (!ordered.some((item) => item.metal_id === metal.metal_id)) {
|
||||
ordered.push(metal)
|
||||
}
|
||||
}
|
||||
|
||||
return ordered
|
||||
}
|
||||
|
||||
export function resolveMetalAssetsDisplayLabel(
|
||||
metal: WalletMetalTotal,
|
||||
isBoursecuLayout: boolean
|
||||
): string {
|
||||
if (!isBoursecuLayout) {
|
||||
return metal.label
|
||||
}
|
||||
|
||||
switch (metal.metal_id) {
|
||||
case METAL_COPPER:
|
||||
return 'دارایی مس'
|
||||
case METAL_GOLD:
|
||||
return 'دارایی طلایی'
|
||||
case METAL_SILVER:
|
||||
return 'دارایی نقره'
|
||||
default:
|
||||
return metal.label
|
||||
}
|
||||
}
|
||||
|
||||
export type HomeAssetRowInfo = 'metal' | 'rial' | 'none'
|
||||
|
||||
export interface HomeAssetRow {
|
||||
id: string
|
||||
label: string
|
||||
value: string
|
||||
metal?: WalletMetalTotal
|
||||
info: HomeAssetRowInfo
|
||||
}
|
||||
|
||||
function formatInventoryCardProductLabel(card: WalletInventoryCard): string {
|
||||
return `دارایی ${card.title}`
|
||||
}
|
||||
|
||||
function findMetalTotalById(metalTotals: WalletMetalTotal[], metalId: number) {
|
||||
return metalTotals.find((metal) => metal.metal_id === metalId)
|
||||
}
|
||||
|
||||
/** boursecu home — هر محصول / فلز / ریال از customer/wallet/assets جدا نمایش داده میشود */
|
||||
export function buildBoursecuHomeAssetRows(
|
||||
summary: {
|
||||
inventory_cards?: WalletInventoryCard[]
|
||||
count_products?: WalletInventoryCard[]
|
||||
metal_totals?: WalletMetalTotal[]
|
||||
total_rial_value?: number
|
||||
} | null | undefined
|
||||
): HomeAssetRow[] {
|
||||
if (!summary) {
|
||||
return []
|
||||
}
|
||||
|
||||
const rows: HomeAssetRow[] = []
|
||||
const metalTotals = summary.metal_totals ?? []
|
||||
const inventoryCards = summary.inventory_cards ?? []
|
||||
const countProducts = summary.count_products ?? []
|
||||
|
||||
for (const card of inventoryCards) {
|
||||
if (card.display_type === 'count') {
|
||||
continue
|
||||
}
|
||||
|
||||
rows.push({
|
||||
id: `inventory-${card.product_id}`,
|
||||
label: formatInventoryCardProductLabel(card),
|
||||
value: formatInventoryCardValue(card),
|
||||
metal: findMetalTotalById(metalTotals, card.metal_id),
|
||||
info: 'metal',
|
||||
})
|
||||
}
|
||||
|
||||
for (const metal of metalTotals) {
|
||||
if (metal.metal_id === METAL_COPPER) {
|
||||
continue
|
||||
}
|
||||
|
||||
rows.push({
|
||||
id: `metal-${metal.metal_id}`,
|
||||
label: resolveMetalAssetsDisplayLabel(metal, true),
|
||||
value: `${Number(metal.total_weight ?? 0)}${metal.unit ?? ''}`,
|
||||
metal,
|
||||
info: 'metal',
|
||||
})
|
||||
}
|
||||
|
||||
for (const card of countProducts) {
|
||||
rows.push({
|
||||
id: `count-${card.product_id}`,
|
||||
label: formatInventoryCardProductLabel(card),
|
||||
value: formatInventoryCardValue(card),
|
||||
metal: findMetalTotalById(metalTotals, card.metal_id),
|
||||
info: 'metal',
|
||||
})
|
||||
}
|
||||
|
||||
rows.push({
|
||||
id: 'rial-total',
|
||||
label: 'دارایی ریالی',
|
||||
value: `${formatRialNumber(summary.total_rial_value ?? 0)} ریال`,
|
||||
info: 'rial',
|
||||
})
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
export function sumInventoryCardsWeight(
|
||||
cards: Array<{ metal_id?: number; quantity?: number }>,
|
||||
metalId: number
|
||||
|
||||
@@ -6,13 +6,37 @@ function productUsesLastPriceIfZero(product: Product): boolean {
|
||||
return Boolean(product.use_last_price_if_zero)
|
||||
}
|
||||
|
||||
function applyPriceOffsetPercent(basePrice: number, offsetPercent?: number | null) {
|
||||
export function resolvePriceOffsetRatio(offsetPercent?: number | null) {
|
||||
const offset = Number(offsetPercent ?? 0)
|
||||
if (!offset) {
|
||||
return 0
|
||||
}
|
||||
|
||||
// 5 => 5% ، 0.05 => 5% (اعشاری)
|
||||
if (Math.abs(offset) > 0 && Math.abs(offset) < 1) {
|
||||
return offset
|
||||
}
|
||||
|
||||
return offset / 100
|
||||
}
|
||||
|
||||
export function applyPriceOffsetPercent(basePrice: number, offsetPercent?: number | null) {
|
||||
const ratio = resolvePriceOffsetRatio(offsetPercent)
|
||||
if (!ratio) {
|
||||
return basePrice
|
||||
}
|
||||
|
||||
return Math.round(basePrice * (1 + offset / 100))
|
||||
return Math.round(basePrice * (1 + ratio))
|
||||
}
|
||||
|
||||
/** boursecu chart: قیمت + (قیمت × offset) */
|
||||
export function applyChartPriceOffset(basePrice: number, offsetPercent?: number | null) {
|
||||
const ratio = resolvePriceOffsetRatio(offsetPercent)
|
||||
if (!ratio) {
|
||||
return basePrice
|
||||
}
|
||||
|
||||
return Math.round(basePrice + basePrice * ratio)
|
||||
}
|
||||
|
||||
/** قیمت پایه بدون کارمزد — اگر صفر باشد نباید فقط cash_fee بهعنوان قیمت نمایش داده شود */
|
||||
|
||||
@@ -17,6 +17,9 @@ export interface SupportMessengerSettings {
|
||||
|
||||
export const supportMessengerSettings = ref<SupportMessengerSettings>({})
|
||||
|
||||
export const DEFAULT_SUPPORT_MESSENGER_TEXT =
|
||||
'جهت شارژ حساب کاربری خود، مبلغ موردنظر را واریز نموده و از طریق پیامرسان به پشتیبانی اطلاع دهید.'
|
||||
|
||||
function getSupportSettingValue(
|
||||
settings: Array<{ key?: string; is_active?: boolean | number; value?: unknown }>,
|
||||
key: string
|
||||
|
||||
Reference in New Issue
Block a user