feat: align fee summary, special pricing and support chat with modular
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+3
-2
@@ -3,7 +3,7 @@ import { useWebSocket } from '@vueuse/core'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { useSettingsStore } from '@/store/settingsStore'
|
||||
import { startChat } from '@/api/chat'
|
||||
import { createUuid } from '@/utils/index'
|
||||
import { createUuid, getToken } from '@/utils/index'
|
||||
|
||||
/** true/1/yes/activate */
|
||||
export function isSettingFlagOn(value: unknown): boolean {
|
||||
@@ -11,8 +11,9 @@ export function isSettingFlagOn(value: unknown): boolean {
|
||||
return v === 'true' || v === '1' || v === 'yes' || v === 'activate'
|
||||
}
|
||||
|
||||
/** نمایش چت برای کاربر نهایی: سوییچ دیپلوی + سوییچ مشتری */
|
||||
/** نمایش چت برای کاربر نهایی: لاگین + سوییچ دیپلوی + سوییچ مشتری */
|
||||
export function isUserSupportChatEnabled(): boolean {
|
||||
if (!getToken()) return false
|
||||
const settings = useSettingsStore()
|
||||
return (
|
||||
isSettingFlagOn(settings.getSettingByKey('show_to_users_chat')) &&
|
||||
|
||||
+2
-1
@@ -7,6 +7,7 @@ import {
|
||||
priceWssEnabled,
|
||||
shouldUsePricePolling,
|
||||
} from '@/utils/priceSocketState'
|
||||
import { applyBoardSpecialToLastPayload } from '@/utils/specialPricing'
|
||||
|
||||
const LAST_INTERVAL_MS = 10000
|
||||
|
||||
@@ -161,7 +162,7 @@ export async function getLastPrices(): Promise<boolean> {
|
||||
}
|
||||
|
||||
export function applyLastPricePayload(dataLast: Price) {
|
||||
const normalized = normalizePriceResponse(dataLast)
|
||||
const normalized = normalizePriceResponse(applyBoardSpecialToLastPayload(dataLast as any) as Price)
|
||||
const nextBuy = getTradeBuyPrice(normalized)
|
||||
const prevBuy = getTradeBuyPrice(lastPrice.value)
|
||||
|
||||
|
||||
@@ -9,6 +9,10 @@ import {
|
||||
priceStreamLifecycle,
|
||||
priceWssEnabled,
|
||||
} from '@/utils/priceSocketState'
|
||||
import {
|
||||
applyBoardSpecialToLastPayload,
|
||||
setSpecialPricingPayload,
|
||||
} from '@/utils/specialPricing'
|
||||
|
||||
export {
|
||||
priceSocketConnected,
|
||||
@@ -326,7 +330,7 @@ function handleLastPayload(payload: unknown) {
|
||||
return
|
||||
}
|
||||
|
||||
const last = payload as Price
|
||||
const last = applyBoardSpecialToLastPayload(payload as Price)
|
||||
if (last.price_last == null && last.price == null && last.buy == null) {
|
||||
return
|
||||
}
|
||||
@@ -413,6 +417,14 @@ function bindStreamEvents(source: EventSource) {
|
||||
}
|
||||
})
|
||||
|
||||
source.addEventListener('special-pricing', (event) => {
|
||||
try {
|
||||
setSpecialPricingPayload(JSON.parse(event.data))
|
||||
} catch (error) {
|
||||
console.warn('Special-pricing SSE parse failed', error)
|
||||
}
|
||||
})
|
||||
|
||||
source.addEventListener('ping', () => {
|
||||
touchStreamActivity()
|
||||
})
|
||||
|
||||
@@ -5,7 +5,7 @@ export type ProductCashFeeSource = {
|
||||
cash_fee_sell_is_percent?: boolean | null
|
||||
}
|
||||
|
||||
/** 0.5 => 0.5% ، 5 => 5% */
|
||||
/** 0.5 => 0.5% ، 5 => 5% — همیشه عدد درصد */
|
||||
export function resolveCashFeePercentRatio(percentValue?: number | null) {
|
||||
const offset = Number(percentValue ?? 0)
|
||||
if (!offset || !Number.isFinite(offset)) {
|
||||
@@ -15,6 +15,11 @@ export function resolveCashFeePercentRatio(percentValue?: number | null) {
|
||||
return offset / 100
|
||||
}
|
||||
|
||||
/** @deprecated از resolveCashFeePercentRatio استفاده کنید */
|
||||
export function resolveFeePercentRatio(percentValue?: number | null) {
|
||||
return resolveCashFeePercentRatio(percentValue)
|
||||
}
|
||||
|
||||
export function resolveProductCashFeeRial(
|
||||
feeValue: number | null | undefined,
|
||||
isPercent: boolean | null | undefined,
|
||||
@@ -38,17 +43,38 @@ export function resolveProductCashFeeRial(
|
||||
}
|
||||
|
||||
export function resolveProductCashFeeBuyRial(product: ProductCashFeeSource, basePriceRial: number) {
|
||||
return resolveProductCashFeeRial(
|
||||
const baseFee = resolveProductCashFeeRial(
|
||||
product.cash_fee_buy,
|
||||
product.cash_fee_buy_is_percent,
|
||||
basePriceRial,
|
||||
)
|
||||
const special = Number((product as any).special_profit_buy ?? 0)
|
||||
return baseFee + (Number.isFinite(special) ? Math.round(special) : 0)
|
||||
}
|
||||
|
||||
export function resolveProductCashFeeSellRial(product: ProductCashFeeSource, basePriceRial: number) {
|
||||
return resolveProductCashFeeRial(
|
||||
const baseFee = resolveProductCashFeeRial(
|
||||
product.cash_fee_sell,
|
||||
product.cash_fee_sell_is_percent,
|
||||
basePriceRial,
|
||||
)
|
||||
const special = Number((product as any).special_profit_sell ?? 0)
|
||||
return baseFee + (Number.isFinite(special) ? Math.round(special) : 0)
|
||||
}
|
||||
|
||||
export function formatProductCashFeeLabel(
|
||||
feeValue: number | null | undefined,
|
||||
isPercent?: boolean | null,
|
||||
) {
|
||||
const raw = Number(feeValue ?? 0)
|
||||
if (!raw) {
|
||||
return '۰'
|
||||
}
|
||||
|
||||
if (isPercent) {
|
||||
const text = Number.isInteger(raw) ? String(raw) : String(parseFloat(raw.toFixed(4)))
|
||||
return `${text}%`
|
||||
}
|
||||
|
||||
return `${raw} ریال`
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ref } from 'vue'
|
||||
import { getSettings } from '@/api/configuration'
|
||||
import { useSettingsStore } from '@/store/settingsStore'
|
||||
|
||||
export const serviceFeePercentage = ref<number>(0.01)
|
||||
|
||||
@@ -68,8 +68,12 @@ function feeRate() {
|
||||
|
||||
export async function loadServiceFeeFromSettings() {
|
||||
try {
|
||||
const settings = await getSettings()
|
||||
if (settings && Array.isArray(settings)) {
|
||||
const settingsStore = useSettingsStore()
|
||||
if (!settingsStore.loaded) {
|
||||
await settingsStore.fetchSettings()
|
||||
}
|
||||
const settings = settingsStore.settings
|
||||
if (settings?.length) {
|
||||
const feeSetting = settings.find(
|
||||
(s) => s?.key === 'fee_service_charge_percentage' && s?.is_active === true
|
||||
)
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
export type SpecialPricingRule = {
|
||||
product_id?: number
|
||||
package_id: number
|
||||
title: string
|
||||
profit_buy: number
|
||||
profit_sell: number
|
||||
show_product: boolean
|
||||
}
|
||||
|
||||
export type SpecialPricingPayload = {
|
||||
enabled?: boolean
|
||||
products?: SpecialPricingRule[]
|
||||
board?: SpecialPricingRule | null
|
||||
}
|
||||
|
||||
const state = ref<{
|
||||
enabled: boolean
|
||||
byProduct: Record<number, SpecialPricingRule>
|
||||
board: SpecialPricingRule | null
|
||||
}>({
|
||||
enabled: false,
|
||||
byProduct: {},
|
||||
board: null,
|
||||
})
|
||||
|
||||
export function setSpecialPricingPayload(payload?: SpecialPricingPayload | null) {
|
||||
if (!payload || !payload.enabled) {
|
||||
state.value = { enabled: false, byProduct: {}, board: null }
|
||||
return
|
||||
}
|
||||
|
||||
const byProduct: Record<number, SpecialPricingRule> = {}
|
||||
for (const row of payload.products ?? []) {
|
||||
const pid = Number(row.product_id ?? 0)
|
||||
if (pid > 0) byProduct[pid] = row
|
||||
}
|
||||
|
||||
state.value = {
|
||||
enabled: true,
|
||||
byProduct,
|
||||
board: payload.board ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
export function getSpecialPricingState() {
|
||||
return state
|
||||
}
|
||||
|
||||
/** اگر سرور قبلاً special_pricing نزده، اختلاف تابلو را روی last اعمال کن */
|
||||
export function applyBoardSpecialToLastPayload<T extends Record<string, any>>(payload: T): T {
|
||||
if (!payload || typeof payload !== 'object') return payload
|
||||
if (payload.special_pricing) return payload
|
||||
|
||||
const board = state.value.board
|
||||
if (!state.value.enabled || !board) return payload
|
||||
|
||||
const buyExtra = Number(board.profit_buy ?? 0)
|
||||
const sellExtra = Number(board.profit_sell ?? 0)
|
||||
if (buyExtra <= 0 && sellExtra <= 0) return payload
|
||||
|
||||
const next = { ...payload } as T & Record<string, any>
|
||||
if (Number(next.buy ?? 0) > 0 && buyExtra > 0) {
|
||||
next.buy = Number(next.buy) + buyExtra
|
||||
}
|
||||
if (Number(next.sell ?? 0) > 0 && sellExtra > 0) {
|
||||
next.sell = Math.max(0, Number(next.sell) - sellExtra)
|
||||
}
|
||||
if (Number(next.price_last ?? 0) > 0 && buyExtra > 0) {
|
||||
next.price_last = Number(next.price_last) + buyExtra
|
||||
}
|
||||
next.special_pricing = {
|
||||
package_id: board.package_id,
|
||||
title: board.title,
|
||||
profit_buy: buyExtra,
|
||||
profit_sell: sellExtra,
|
||||
}
|
||||
return next
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ref } from 'vue'
|
||||
import { getSettings } from '@/api/configuration'
|
||||
import type { DepositGuideVars } from '@/utils/depositGuideText'
|
||||
import {
|
||||
DEFAULT_DEPOSIT_GUIDE_TEXT,
|
||||
@@ -366,10 +365,8 @@ export async function loadSupportMessengerFromSettings(forceSettingsRefresh = fa
|
||||
await settingsStore.fetchSettings()
|
||||
}
|
||||
|
||||
const settings = await getSettings()
|
||||
if (!settings || !Array.isArray(settings)) return
|
||||
|
||||
settingsStore.mergeSettings(settings)
|
||||
const settings = settingsStore.settings
|
||||
if (!settings?.length) return
|
||||
|
||||
supportMessengerSettings.value = {
|
||||
messengers: resolveSupportMessengersFromSettings(settings),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { getSettings } from '@/api/configuration'
|
||||
import type { AppSetting } from '@/store/settingsStore'
|
||||
import { useSettingsStore } from '@/store/settingsStore'
|
||||
|
||||
@@ -90,13 +89,6 @@ export async function fetchTrackingSettings(force = false): Promise<AppSetting[]
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
try {
|
||||
const freshSettings = normalizeSettingsResponse(await getSettings())
|
||||
if (freshSettings.length > 0) settingsStore.mergeSettings(freshSettings)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
trackingSettingsCache = [...settingsStore.settings]
|
||||
return trackingSettingsCache
|
||||
})()
|
||||
|
||||
Reference in New Issue
Block a user