safavi works
This commit is contained in:
+15
-1
@@ -28,7 +28,21 @@ export async function getDefaultAddress(): Promise<Address | null> {
|
||||
}
|
||||
|
||||
export async function getProvince() {
|
||||
return await useAxios.get(`api/city/state`)
|
||||
const data = await useAxios.get(`api/city/state`)
|
||||
if (Array.isArray(data)) {
|
||||
return data
|
||||
}
|
||||
if (data && typeof data === 'object') {
|
||||
const rows = (data as { rows?: unknown[] }).rows
|
||||
if (Array.isArray(rows)) {
|
||||
return rows
|
||||
}
|
||||
const nested = (data as { data?: unknown[] }).data
|
||||
if (Array.isArray(nested)) {
|
||||
return nested
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export async function getCity(id: number) {
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
@import (less) './boursecu-theme.less';
|
||||
@import (less) '../../layouts/styles/desktop-shell.less';
|
||||
@import (less) '../../layouts/styles/mobile-frame.less';
|
||||
@import (less) '../../layouts/styles/mobile-page-common.less';
|
||||
@import (less) '../../layouts/desktop/styles/desktop-page-common.less';
|
||||
@import (less) '../../views/login/desktop/styles/desktop-auth.less';
|
||||
@import (less) '../../views/base/user/desktop/styles/desktop-user.less';
|
||||
@import (less) '../../views/base/products/desktop/styles/desktop-products.less';
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
</style>-->
|
||||
<template>
|
||||
<VanActionSheet teleport="body" :z-index="zIndex">
|
||||
<div class="justify-center cursor-pointer flex mt-1" @click="$emit('update:show', false)">
|
||||
<VanActionSheet v-model:show="show" teleport="body" :z-index="zIndex">
|
||||
<div class="justify-center cursor-pointer flex mt-1" @click="show = false">
|
||||
<span class="rounded-sm w-12 border-t-2 border-t-solid border-t-gray-500 mt-2"></span>
|
||||
</div>
|
||||
<div class="mx-4 mb-5 mt-3">
|
||||
@@ -54,7 +54,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
interface Props {
|
||||
show?: boolean
|
||||
cancelText?: string
|
||||
showButton?: boolean
|
||||
showCancel?: boolean
|
||||
@@ -65,6 +68,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
show: false,
|
||||
cancelText: 'بازگشت',
|
||||
showButton: false,
|
||||
showCancel: true,
|
||||
@@ -72,6 +76,17 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
okText: 'ثبت',
|
||||
zIndex: 5200, // --app-z-action-sheet
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
cancel: []
|
||||
ok: []
|
||||
'update:show': [value: boolean]
|
||||
}>()
|
||||
|
||||
const show = computed({
|
||||
get: () => props.show,
|
||||
set: (value: boolean) => emit('update:show', value),
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
:placeholder="props.placeholder"
|
||||
:allowClear="props.allowClear"
|
||||
:filter-option="filterOption"
|
||||
:get-popup-container="getPopupContainer"
|
||||
:dropdown-style="dropdownStyle"
|
||||
class="w-full"
|
||||
>
|
||||
<template #notFoundContent v-if="noData">
|
||||
@@ -60,12 +62,15 @@ interface Prop {
|
||||
allowClear?: boolean
|
||||
hasTooltip?: boolean
|
||||
noData?: string
|
||||
/** بالاتر از action sheet / modal برای نمایش درست dropdown */
|
||||
inOverlay?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Prop>(), {
|
||||
allowClear: true,
|
||||
hasTooltip: true,
|
||||
showSearch: true
|
||||
showSearch: true,
|
||||
inOverlay: true,
|
||||
})
|
||||
|
||||
// console.log('props.items ====>', props.items)
|
||||
@@ -94,6 +99,17 @@ const handleChange = (e: any) => {
|
||||
)
|
||||
}
|
||||
|
||||
const dropdownStyle = computed(() =>
|
||||
props.inOverlay ? { zIndex: 5300 } : undefined,
|
||||
)
|
||||
|
||||
function getPopupContainer(triggerNode: HTMLElement) {
|
||||
if (!props.inOverlay) {
|
||||
return triggerNode.parentElement ?? document.body
|
||||
}
|
||||
return document.body
|
||||
}
|
||||
|
||||
//css style
|
||||
// const { prefixCls } = usePrefix('select')
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<header class="dpage-topbar">
|
||||
<button
|
||||
v-if="showBack"
|
||||
type="button"
|
||||
class="dpage-topbar__back"
|
||||
@click="handleBack"
|
||||
>
|
||||
<BasicIcon icon="vuesax-linear:arrow-right-1" size="16" />
|
||||
<span>بازگشت</span>
|
||||
</button>
|
||||
<div class="dpage-topbar__meta">
|
||||
<p v-if="eyebrow" class="dpage-topbar__eyebrow">{{ eyebrow }}</p>
|
||||
<h1 class="dpage-topbar__title">{{ title }}</h1>
|
||||
<p v-if="subtitle" class="dpage-topbar__subtitle">{{ subtitle }}</p>
|
||||
</div>
|
||||
<div v-if="$slots.trailing">
|
||||
<slot name="trailing" />
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import router from '@/router'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
title: string
|
||||
eyebrow?: string
|
||||
subtitle?: string
|
||||
showBack?: boolean
|
||||
}>(),
|
||||
{
|
||||
showBack: false,
|
||||
},
|
||||
)
|
||||
|
||||
function handleBack() {
|
||||
router.back()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div class="mpage-header wrap-fix">
|
||||
<button
|
||||
v-if="showBack"
|
||||
type="button"
|
||||
class="mpage-header__back"
|
||||
@click="handleBack"
|
||||
>
|
||||
<BasicIcon icon="vuesax-linear:arrow-right-1" />
|
||||
</button>
|
||||
<div class="mpage-header__title">{{ title }}</div>
|
||||
<div v-if="$slots.trailing" class="mpage-header__trailing">
|
||||
<slot name="trailing" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import router from '@/router'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
title: string
|
||||
showBack?: boolean
|
||||
}>(),
|
||||
{
|
||||
showBack: false,
|
||||
},
|
||||
)
|
||||
|
||||
function handleBack() {
|
||||
router.back()
|
||||
}
|
||||
</script>
|
||||
@@ -33,7 +33,7 @@ const BOURSECU_PRIMARY_NAV: AppNavItem[] = [
|
||||
|
||||
const DEFAULT_PRIMARY_NAV: AppNavItem[] = [
|
||||
{ id: 'home', routeName: 'home', label: 'صفحه اصلی', icon: 'vuesax-linear:home' },
|
||||
{ id: 'transactions', routeName: 'transactions', label: 'گزارش', icon: 'vuesax-linear:receipt-item' },
|
||||
{ id: 'transactions', routeName: 'transactions', label: 'تراکنشها', icon: 'vuesax-linear:receipt-item' },
|
||||
{ id: 'buyProduct', routeName: 'buyProduct', label: 'محصولات', icon: 'vuesax-linear:coin' },
|
||||
{ id: 'wallet', routeName: 'wallet', label: 'کیف پول', icon: 'vuesax-linear:wallet-1' },
|
||||
{ id: 'user', routeName: 'user', label: 'تنظیمات', icon: 'vuesax-linear:setting-2' },
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
class="tab__icon-image"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-xs mt-1 font-bold">سفارش ها</div>
|
||||
<div class="text-xs mt-1 font-bold">خرید و فروش</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
@@ -42,7 +42,7 @@
|
||||
/>
|
||||
<Icon v-else icon="vuesax-linear:receipt-item" size="24" />
|
||||
</div>
|
||||
<div class="text-xs mt-1 font-bold">{{ isBoursecu ? 'تراکنش ها' : 'گزارش' }}</div>
|
||||
<div class="text-xs mt-1 font-bold">تراکنش ها</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/* Shared mobile page chrome */
|
||||
|
||||
.mpage-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
background: #fff;
|
||||
color: @App-primary-color;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.5;
|
||||
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08);
|
||||
z-index: 1999;
|
||||
|
||||
&__back {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
&__title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__trailing {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mpage-subheader {
|
||||
top: 2.75rem;
|
||||
z-index: 1999;
|
||||
padding: 0.75rem;
|
||||
background: #fff;
|
||||
color: @App-primary-color;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.mpage-content {
|
||||
padding-top: 4rem;
|
||||
}
|
||||
|
||||
.mpage-content--with-subheader {
|
||||
padding-top: 6.5rem;
|
||||
}
|
||||
|
||||
.mpage-scroll {
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 50px);
|
||||
}
|
||||
@@ -43,4 +43,6 @@ export interface WalletAssetsSummary {
|
||||
metal_totals: WalletMetalTotal[]
|
||||
assets_rial_value: number
|
||||
total_rial_value: number
|
||||
/** boursecu — سود یا زیان کل دارایی (مثبت = سود، منفی = زیان) */
|
||||
profit_loss?: number
|
||||
}
|
||||
|
||||
+178
-178
@@ -5,7 +5,7 @@ import type { ProductFile } from '@/utils/file'
|
||||
import type { WalletInventoryCard, WalletMetalTotal } from '@/models/walletAssets'
|
||||
import { getFileUrl } from '@/utils/file'
|
||||
import { isCountBasedProduct, getProductQuantityUnit, type ProductUnitSource } from '@/utils/productDealMethod'
|
||||
import { isMeltedTradeGroup, normalizeProductGroupType, LEGACY_GROUP_KATOD } from '@/utils/productCategories'
|
||||
import { isMeltedTradeGroup, normalizeProductGroupType, LEGACY_GROUP_KATOD, LEGACY_GROUP_MAFTOL } from '@/utils/productCategories'
|
||||
import defaultImg from '@/assets/img/defaultAbshodeh.svg'
|
||||
import goyImg from '@/assets/img/goyImg.svg'
|
||||
import mokaabImg from '@/assets/img/mokaabImg.svg'
|
||||
@@ -95,24 +95,19 @@ export function resolveMetalAssetsDisplayLabel(
|
||||
}
|
||||
}
|
||||
|
||||
export type HomeAssetRowInfo = 'metal' | 'rial' | 'none'
|
||||
export type BoursecuHomeAssetRowId = 'copper-total' | 'rial-total'
|
||||
|
||||
export interface HomeAssetRow {
|
||||
id: string
|
||||
export interface BoursecuAssetPopupLine {
|
||||
label: string
|
||||
value: string
|
||||
metal?: WalletMetalTotal
|
||||
info: HomeAssetRowInfo
|
||||
/** boursecu — متن مودال توضیحات (info) */
|
||||
modalText?: string
|
||||
/** boursecu — عنوان مودال (مثلاً کل دارایی مس) */
|
||||
modalTitle?: string
|
||||
/** boursecu — محتوای چندخطی مودال */
|
||||
modalContent?: string
|
||||
tone?: 'profit' | 'loss' | 'default'
|
||||
}
|
||||
|
||||
function formatInventoryCardProductLabel(card: WalletInventoryCard): string {
|
||||
return `دارایی ${card.title}`
|
||||
export interface BoursecuHomeAssetRow {
|
||||
id: BoursecuHomeAssetRowId
|
||||
label: string
|
||||
value: string
|
||||
popupLines: BoursecuAssetPopupLine[]
|
||||
}
|
||||
|
||||
/** boursecu — متن مودال برای ردیفهای metal_totals */
|
||||
@@ -129,6 +124,44 @@ export function formatBoursecuMetalModalText(metal: WalletMetalTotal): string {
|
||||
}
|
||||
}
|
||||
|
||||
function isBoursecuKatodCard(card: WalletInventoryCard) {
|
||||
const group = normalizeProductGroupType(card.group_type)
|
||||
return group === LEGACY_GROUP_KATOD || (card.title ?? '').includes('کاتد')
|
||||
}
|
||||
|
||||
function isBoursecuMaftolCard(card: WalletInventoryCard) {
|
||||
const group = normalizeProductGroupType(card.group_type)
|
||||
return group === LEGACY_GROUP_MAFTOL || (card.title ?? '').includes('مفتول')
|
||||
}
|
||||
|
||||
function isBoursecuShamshCard(card: WalletInventoryCard) {
|
||||
return (card.title ?? '').includes('شمش')
|
||||
}
|
||||
|
||||
function findBoursecuProductCard(
|
||||
inventoryCards: WalletInventoryCard[],
|
||||
countProducts: WalletInventoryCard[],
|
||||
matcher: (card: WalletInventoryCard) => boolean
|
||||
) {
|
||||
return inventoryCards.find(matcher) ?? countProducts.find(matcher)
|
||||
}
|
||||
|
||||
function resolveBoursecuCardQuantityValue(
|
||||
card: WalletInventoryCard | undefined,
|
||||
metal: WalletMetalTotal | undefined,
|
||||
defaultUnit: string
|
||||
): string {
|
||||
if (card) {
|
||||
return formatInventoryCardValue(card)
|
||||
}
|
||||
|
||||
if (metal) {
|
||||
return formatBoursecuMetalRowValue(metal)
|
||||
}
|
||||
|
||||
return `0 ${defaultUnit}`
|
||||
}
|
||||
|
||||
/** boursecu — جمع موجودی کاتد، مفتول و شمش از کارتهای دارایی صفحه اصلی */
|
||||
function sumBoursecuHomeProductInventories(
|
||||
inventoryCards: WalletInventoryCard[],
|
||||
@@ -159,91 +192,6 @@ function sumBoursecuHomeProductInventories(
|
||||
return sum
|
||||
}
|
||||
|
||||
type BoursecuModalProduct = {
|
||||
title: string
|
||||
quantity: number
|
||||
unit: string
|
||||
}
|
||||
|
||||
function toBoursecuModalProduct(card: WalletInventoryCard): BoursecuModalProduct {
|
||||
return {
|
||||
title: card.title,
|
||||
quantity: Number(card.quantity ?? 0),
|
||||
unit: card.unit ?? (card.display_type === 'count' ? 'عدد' : 'کیلوگرم'),
|
||||
}
|
||||
}
|
||||
|
||||
function resolveBoursecuMetalModalProduct(
|
||||
metal: WalletMetalTotal,
|
||||
countProducts: WalletInventoryCard[]
|
||||
): BoursecuModalProduct {
|
||||
const countCard =
|
||||
countProducts.find((card) => Number(card.metal_id) === metal.metal_id) ??
|
||||
countProducts.find((card) => card.title?.includes('شمش'))
|
||||
|
||||
if (countCard) {
|
||||
return toBoursecuModalProduct(countCard)
|
||||
}
|
||||
|
||||
if (metal.metal_id === METAL_GOLD) {
|
||||
return {
|
||||
title: 'شمش مس',
|
||||
quantity: Number(metal.total_weight ?? 0),
|
||||
unit: 'عدد',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
title: metal.label,
|
||||
quantity: Number(metal.total_weight ?? 0),
|
||||
unit: metal.unit ?? 'کیلوگرم',
|
||||
}
|
||||
}
|
||||
|
||||
/** boursecu — جزئیات کل دارایی مس در مودال info */
|
||||
export function formatBoursecuCopperAssetsModalContent(
|
||||
metal: WalletMetalTotal,
|
||||
inventoryCards: WalletInventoryCard[],
|
||||
countProducts: WalletInventoryCard[],
|
||||
product: BoursecuModalProduct
|
||||
): string {
|
||||
const weightUnit = metal.unit || 'کیلوگرم'
|
||||
const productWeight = usePriceWithoutType(
|
||||
sumBoursecuHomeProductInventories(inventoryCards, countProducts),
|
||||
false
|
||||
)
|
||||
const clickedQuantity = usePriceWithoutType(product.quantity, false)
|
||||
|
||||
return [
|
||||
`مجموع کیف محصولات: ${productWeight} ${weightUnit}`,
|
||||
`موجودی ${product.title}: ${clickedQuantity} ${product.unit}`,
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
function withBoursecuCopperModal(
|
||||
row: Omit<HomeAssetRow, 'modalTitle' | 'modalContent'>,
|
||||
copperTotal: WalletMetalTotal | undefined,
|
||||
inventoryCards: WalletInventoryCard[],
|
||||
countProducts: WalletInventoryCard[],
|
||||
product: BoursecuModalProduct
|
||||
): HomeAssetRow {
|
||||
if (!copperTotal) {
|
||||
return row
|
||||
}
|
||||
|
||||
return {
|
||||
...row,
|
||||
metal: copperTotal,
|
||||
modalTitle: getTotalMetalLabel(METAL_COPPER),
|
||||
modalContent: formatBoursecuCopperAssetsModalContent(
|
||||
copperTotal,
|
||||
inventoryCards,
|
||||
countProducts,
|
||||
product
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
function findMetalTotalById(metalTotals: WalletMetalTotal[], metalId: number) {
|
||||
return metalTotals.find((metal) => metal.metal_id === metalId)
|
||||
}
|
||||
@@ -256,95 +204,151 @@ function formatBoursecuMetalRowValue(metal: WalletMetalTotal): string {
|
||||
return `${amount}${metal.unit ?? ''}`
|
||||
}
|
||||
|
||||
/** boursecu home — هر محصول / فلز / ریال از customer/wallet/assets جدا نمایش داده میشود */
|
||||
function formatRialNumber(value: number): string {
|
||||
return usePriceWithoutType(Math.round(value))
|
||||
}
|
||||
|
||||
function formatBoursecuProfitLossLine(profitLoss?: number): BoursecuAssetPopupLine {
|
||||
const amount = Number(profitLoss ?? 0)
|
||||
|
||||
if (amount > 0) {
|
||||
return {
|
||||
label: 'میزان سود یا زیان',
|
||||
value: `${formatRialNumber(amount)} ریال (سود)`,
|
||||
tone: 'profit',
|
||||
}
|
||||
}
|
||||
|
||||
if (amount < 0) {
|
||||
return {
|
||||
label: 'میزان سود یا زیان',
|
||||
value: `${formatRialNumber(Math.abs(amount))} ریال (زیان)`,
|
||||
tone: 'loss',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
label: 'میزان سود یا زیان',
|
||||
value: `${formatRialNumber(0)} ریال`,
|
||||
}
|
||||
}
|
||||
|
||||
function buildBoursecuCopperPopupLines(summary: {
|
||||
inventory_cards?: WalletInventoryCard[]
|
||||
count_products?: WalletInventoryCard[]
|
||||
metal_totals?: WalletMetalTotal[]
|
||||
}): BoursecuAssetPopupLine[] {
|
||||
const inventoryCards = summary.inventory_cards ?? []
|
||||
const countProducts = summary.count_products ?? []
|
||||
const metalTotals = summary.metal_totals ?? []
|
||||
const copperMetal = findMetalTotalById(metalTotals, METAL_COPPER)
|
||||
const shamshMetal = findMetalTotalById(metalTotals, METAL_GOLD)
|
||||
const weightUnit = copperMetal?.unit || 'کیلوگرم'
|
||||
const totalWeight = sumBoursecuHomeProductInventories(inventoryCards, countProducts)
|
||||
|
||||
const katodCard = findBoursecuProductCard(inventoryCards, countProducts, isBoursecuKatodCard)
|
||||
const maftolCard = findBoursecuProductCard(inventoryCards, countProducts, isBoursecuMaftolCard)
|
||||
const shamshCard = findBoursecuProductCard(inventoryCards, countProducts, isBoursecuShamshCard)
|
||||
|
||||
return [
|
||||
{
|
||||
label: 'دارایی کاتد مس',
|
||||
value: resolveBoursecuCardQuantityValue(katodCard, undefined, 'کیلوگرم'),
|
||||
},
|
||||
{
|
||||
label: 'دارایی مفتول مس',
|
||||
value: resolveBoursecuCardQuantityValue(maftolCard, undefined, 'کیلوگرم'),
|
||||
},
|
||||
{
|
||||
label: 'دارایی شمش مس',
|
||||
value: resolveBoursecuCardQuantityValue(shamshCard, shamshMetal, 'عدد'),
|
||||
},
|
||||
{
|
||||
label: 'مجموع کل دارایی مس',
|
||||
value: `${usePriceWithoutType(totalWeight, false)} ${weightUnit}`,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
function buildBoursecuRialPopupLines(
|
||||
summary: {
|
||||
cash_wallet?: number
|
||||
total_rial_value?: number
|
||||
assets_rial_value?: number
|
||||
metal_totals?: WalletMetalTotal[]
|
||||
profit_loss?: number
|
||||
},
|
||||
liveCopperPrice: number
|
||||
): BoursecuAssetPopupLine[] {
|
||||
const cash = Number(summary.cash_wallet ?? 0)
|
||||
const metalRial = resolveAssetsRialValue(summary, liveCopperPrice)
|
||||
const total =
|
||||
Number(summary.total_rial_value ?? 0) > 0
|
||||
? Number(summary.total_rial_value)
|
||||
: cash + metalRial
|
||||
|
||||
return [
|
||||
{
|
||||
label: 'کیف پول ریالی',
|
||||
value: `${formatRialNumber(cash)} ریال`,
|
||||
},
|
||||
{
|
||||
label: 'ارزش ریالی مس (وزن × قیمت زنده)',
|
||||
value: `${formatRialNumber(metalRial)} ریال`,
|
||||
},
|
||||
{
|
||||
label: 'مجموع کل دارایی ریالی',
|
||||
value: `${formatRialNumber(total)} ریال`,
|
||||
},
|
||||
formatBoursecuProfitLossLine(summary.profit_loss),
|
||||
]
|
||||
}
|
||||
|
||||
/** boursecu home — دو ردیف کل دارایی مسی و ریالی */
|
||||
export function buildBoursecuHomeAssetRows(
|
||||
summary: {
|
||||
inventory_cards?: WalletInventoryCard[]
|
||||
count_products?: WalletInventoryCard[]
|
||||
metal_totals?: WalletMetalTotal[]
|
||||
cash_wallet?: number
|
||||
total_rial_value?: number
|
||||
} | null | undefined
|
||||
): HomeAssetRow[] {
|
||||
assets_rial_value?: number
|
||||
profit_loss?: number
|
||||
} | null | undefined,
|
||||
liveCopperPrice = 0
|
||||
): BoursecuHomeAssetRow[] {
|
||||
if (!summary) {
|
||||
return []
|
||||
}
|
||||
|
||||
const rows: HomeAssetRow[] = []
|
||||
const metalTotals = summary.metal_totals ?? []
|
||||
const inventoryCards = summary.inventory_cards ?? []
|
||||
const countProducts = summary.count_products ?? []
|
||||
const copperTotal = findMetalTotalById(metalTotals, METAL_COPPER)
|
||||
const metalTotals = summary.metal_totals ?? []
|
||||
const copperMetal = findMetalTotalById(metalTotals, METAL_COPPER)
|
||||
const weightUnit = copperMetal?.unit || 'کیلوگرم'
|
||||
const totalCopperWeight = sumBoursecuHomeProductInventories(inventoryCards, countProducts)
|
||||
|
||||
for (const card of inventoryCards) {
|
||||
if (card.display_type === 'count') {
|
||||
continue
|
||||
}
|
||||
const cash = Number(summary.cash_wallet ?? 0)
|
||||
const metalRial = resolveAssetsRialValue(summary, liveCopperPrice)
|
||||
const totalRial =
|
||||
Number(summary.total_rial_value ?? 0) > 0
|
||||
? Number(summary.total_rial_value)
|
||||
: cash + metalRial
|
||||
|
||||
rows.push(
|
||||
withBoursecuCopperModal(
|
||||
{
|
||||
id: `inventory-${card.product_id}`,
|
||||
label: formatInventoryCardProductLabel(card),
|
||||
value: formatInventoryCardValue(card),
|
||||
metal: findMetalTotalById(metalTotals, card.metal_id),
|
||||
info: 'metal',
|
||||
},
|
||||
copperTotal,
|
||||
inventoryCards,
|
||||
countProducts,
|
||||
toBoursecuModalProduct(card)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
for (const metal of metalTotals) {
|
||||
if (metal.metal_id === METAL_COPPER) {
|
||||
continue
|
||||
}
|
||||
|
||||
rows.push(
|
||||
withBoursecuCopperModal(
|
||||
{
|
||||
id: `metal-${metal.metal_id}`,
|
||||
label: resolveMetalAssetsDisplayLabel(metal, true),
|
||||
value: formatBoursecuMetalRowValue(metal),
|
||||
metal,
|
||||
info: 'metal',
|
||||
},
|
||||
copperTotal,
|
||||
inventoryCards,
|
||||
countProducts,
|
||||
resolveBoursecuMetalModalProduct(metal, countProducts)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
for (const card of countProducts) {
|
||||
rows.push(
|
||||
withBoursecuCopperModal(
|
||||
{
|
||||
id: `count-${card.product_id}`,
|
||||
label: formatInventoryCardProductLabel(card),
|
||||
value: formatInventoryCardValue(card),
|
||||
metal: findMetalTotalById(metalTotals, card.metal_id),
|
||||
info: 'metal',
|
||||
},
|
||||
copperTotal,
|
||||
inventoryCards,
|
||||
countProducts,
|
||||
toBoursecuModalProduct(card)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
rows.push({
|
||||
id: 'rial-total',
|
||||
label: 'دارایی ریالی',
|
||||
value: `${formatRialNumber(summary.cash_wallet ?? 0)} ریال`,
|
||||
info: 'rial',
|
||||
})
|
||||
|
||||
return rows
|
||||
return [
|
||||
{
|
||||
id: 'copper-total',
|
||||
label: 'کل دارایی مسی',
|
||||
value: `${usePriceWithoutType(totalCopperWeight, false)} ${weightUnit}`,
|
||||
popupLines: buildBoursecuCopperPopupLines(summary),
|
||||
},
|
||||
{
|
||||
id: 'rial-total',
|
||||
label: 'کل دارایی ریالی',
|
||||
value: `${formatRialNumber(totalRial)} ریال`,
|
||||
popupLines: buildBoursecuRialPopupLines(summary, liveCopperPrice),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export function sumInventoryCardsWeight(
|
||||
@@ -390,10 +394,6 @@ export function formatMetalAssetsInfoText(metal: WalletMetalTotal): string {
|
||||
return `${metal.label}: مجموع کیف محصولات (${metal.product_weight} ${unit}) + موجودی کیف اصلی (${metal.melted_weight} ${unit})`
|
||||
}
|
||||
|
||||
function formatRialNumber(value: number): string {
|
||||
return usePriceWithoutType(Math.round(value))
|
||||
}
|
||||
|
||||
/** متن توضیح مودال دارایی ریالی */
|
||||
export function formatRialAssetsInfoText(cashWallet: number, metalRialValue: number): string {
|
||||
const total = cashWallet + metalRialValue
|
||||
|
||||
@@ -1,81 +1,71 @@
|
||||
<template>
|
||||
<div >
|
||||
<a-form ref="formRef" :model="address" class="!mt-5" name="basic"
|
||||
@finish="ok()">
|
||||
<div>
|
||||
<a-form ref="formRef" :model="address" class="!mt-5" name="basic" @finish="ok()">
|
||||
<div class="font-bold text-lg mb-2">آدرس</div>
|
||||
<div class="flex flex-col gap-4 pb-30 w-full h-65vh overflow-y-auto">
|
||||
<a-form-item label="استان" name="province" :rules="[required()]">
|
||||
<BasicSelect
|
||||
v-model:value="address.province"
|
||||
:items="provinceList"
|
||||
@change="changeProvince"
|
||||
valueName="title"
|
||||
keyName="title"
|
||||
v-model:value="address.province"
|
||||
:items="provinceList"
|
||||
value-name="title"
|
||||
key-name="title"
|
||||
:has-tooltip="false"
|
||||
@change="changeProvince"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="شهر" name="city" :rules="[required()]">
|
||||
<BasicSelect
|
||||
:disabled="!cityList"
|
||||
v-model:value="address.city"
|
||||
valueName="title"
|
||||
:items="cityList"
|
||||
keyName="title"
|
||||
:disabled="!cityList?.length"
|
||||
v-model:value="address.city"
|
||||
value-name="title"
|
||||
:items="cityList"
|
||||
key-name="title"
|
||||
:has-tooltip="false"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="کد پستی" name="zip" :rules="[required()]">
|
||||
<a-form-item label="کد پستی" name="zip" :rules="[required()]">
|
||||
<BasicInput placeholder="کد پستی را وارد نمایید" v-model:value="address.zip" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="تلفن تماس" name="phone" :rules="[required()]"
|
||||
>
|
||||
<a-form-item label="تلفن تماس" name="phone" :rules="[required()]">
|
||||
<BasicInput placeholder="تلفن تماس را وارد نمایید" v-model:value="address.phone" />
|
||||
</a-form-item>
|
||||
<!-- <FormItem label="کد پستی" name="">
|
||||
<Input placeholder="کد پستی را وارد نمایید" />
|
||||
</FormItem>-->
|
||||
<a-form-item
|
||||
label="آدرس کامل"
|
||||
name="address"
|
||||
:rules="[required()]"
|
||||
>
|
||||
<a-form-item label="آدرس کامل" name="address" :rules="[required()]">
|
||||
<BasicTextArea placeholder="آدرس کامل را وارد نمایید" v-model:value="address.address" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
</a-form>
|
||||
<div class="absolute bottom-18 right-0 w-full p-4 bg-white flex justify-center !text-white ">
|
||||
<BasicButton class=" w-full !text-white " type="primary" @click="handleOk">ثبت آدرس
|
||||
</BasicButton>
|
||||
<div class="absolute bottom-18 right-0 w-full p-4 bg-white flex justify-center !text-white">
|
||||
<BasicButton class="w-full !text-white" type="primary" @click="handleOk">ثبت آدرس</BasicButton>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { required } from '@/utils/inputRules'
|
||||
import BasicSelect from '@/components/BasicPacks/BasicSelect.vue'
|
||||
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||
import BasicInput from '@/components/BasicPacks/BasicInput.vue'
|
||||
import BasicTextArea from '@/components/BasicPacks/BasicTextArea.vue'
|
||||
import { loading } from '@/utils'
|
||||
import { createAddress, getCity, updateAddress } from '@/api/address'
|
||||
import { FormInstance, message } from 'ant-design-vue'
|
||||
import { Address } from '@/models/address'
|
||||
import { addressProvinceList, loadAddressProvinces } from '@/views/base/address/composables/useAddressPageData'
|
||||
|
||||
import {required} from "@/utils/inputRules";
|
||||
import BasicSelect from "@/components/BasicPacks/BasicSelect.vue";
|
||||
import BasicButton from "@/components/BasicPacks/BasicButton.vue";
|
||||
import BasicInput from "@/components/BasicPacks/BasicInput.vue";
|
||||
import BasicTextArea from "@/components/BasicPacks/BasicTextArea.vue";
|
||||
import {loading} from "@/utils";
|
||||
import {createAddress, getCity, getProvince, updateAddress} from "@/api/address";
|
||||
import {FormInstance, message} from "ant-design-vue";
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
visible?: boolean
|
||||
}>(),
|
||||
{
|
||||
visible: false,
|
||||
},
|
||||
)
|
||||
|
||||
import {Address} from "@/models/address";
|
||||
import {createPriceTrigger, updatePriceTrigger} from "@/api/triggerPrice";
|
||||
import {onMounted, ref} from "vue";
|
||||
const formRef = ref<FormInstance>()
|
||||
const provinceList = ref<any[]>([])
|
||||
const cityList = ref<any[]>()
|
||||
const emits = defineEmits([ 'onOk'])
|
||||
const addAddressVisible=ref<boolean>(false)
|
||||
const emits = defineEmits(['onOk'])
|
||||
const editId = ref<number | undefined>(undefined)
|
||||
const address = ref<Address>({
|
||||
city: '',
|
||||
@@ -83,38 +73,62 @@ const address = ref<Address>({
|
||||
phone: '',
|
||||
address: '',
|
||||
zip: '',
|
||||
default_address: false
|
||||
default_address: false,
|
||||
})
|
||||
|
||||
const provinceList = computed(() => addressProvinceList.value)
|
||||
|
||||
function resetFields() {
|
||||
editId.value = undefined
|
||||
cityList.value = undefined
|
||||
address.value = {
|
||||
city: '',
|
||||
province: '',
|
||||
phone: '',
|
||||
address: '',
|
||||
zip: '',
|
||||
default_address: false
|
||||
default_address: false,
|
||||
}
|
||||
}
|
||||
onMounted(async () => {
|
||||
resetFields()
|
||||
provinceList.value = await getProvince()
|
||||
})
|
||||
|
||||
async function loadProvinces() {
|
||||
try {
|
||||
await loadAddressProvinces()
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
message.error('خطا در دریافت لیست استانها')
|
||||
}
|
||||
}
|
||||
|
||||
const handleOk = (e: MouseEvent) => {
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
resetFields()
|
||||
void loadProvinces()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const handleOk = () => {
|
||||
formRef.value?.validate().then(async () => {
|
||||
editId.value ? await updateAddress(editId.value, address.value) : await createAddress(address.value)
|
||||
addAddressVisible.value = false
|
||||
editId.value
|
||||
? await updateAddress(editId.value, address.value)
|
||||
: await createAddress(address.value)
|
||||
message.success('آدرس با موفقیت ثبت شد')
|
||||
emits('onOk')
|
||||
resetFields()
|
||||
}).catch((error) => {
|
||||
console.log('error', error)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('error', error)
|
||||
})
|
||||
}
|
||||
|
||||
const changeProvince = async (obj: any) => {
|
||||
if (!obj?.id) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
loading.value = true
|
||||
cityList.value = undefined
|
||||
@@ -128,23 +142,6 @@ const changeProvince = async (obj: any) => {
|
||||
}
|
||||
|
||||
async function ok() {
|
||||
formRef.value?.validateFields().then(async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
editId.value ? await updatePriceTrigger(editId.value, address.value) : await createPriceTrigger(address.value)
|
||||
message.success('آدرس با موفقیت ایجاد شد')
|
||||
addAddressVisible.value = false
|
||||
emits('onOk')
|
||||
//
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
handleOk()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -39,6 +39,10 @@ async function loadProvinces() {
|
||||
addressProvinceList.value = await getProvince()
|
||||
}
|
||||
|
||||
export async function loadAddressProvinces(force = false) {
|
||||
return runOnceAsync('address:provinces', () => loadProvinces(), force)
|
||||
}
|
||||
|
||||
export async function initAddressPageData(force = false) {
|
||||
if (!force && initialized.value) {
|
||||
return
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="bg-white py-3 px-3 text-primary font-bold drop-shadow wrap-fix flex z-10">
|
||||
<div @click="router.back()">
|
||||
<BasicIcon icon="vuesax-linear:arrow-right-1" />
|
||||
</div>
|
||||
<div class="mr-2">آدرس ها</div>
|
||||
</div>
|
||||
<MobilePageHeader title="آدرس ها" show-back />
|
||||
<div>
|
||||
<img :src="img" alt="" class="mt-2 w-full absolute bottom-0" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
:class="loading ? 'h-screen' : ''"
|
||||
class="px-3 pt-16 pb-2 overflow-y-scroll"
|
||||
style="max-height: calc(100vh - 50px)"
|
||||
class="px-3 mpage-content pb-2 overflow-y-scroll mpage-scroll"
|
||||
>
|
||||
<div
|
||||
v-if="isBazretalaProject()"
|
||||
@@ -54,9 +48,9 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import MobilePageHeader from '@/layouts/components/MobilePageHeader.vue'
|
||||
import TransactionSkeleton from '@/components/TransactionSkeleton.vue'
|
||||
import img from '@/assets/img/user_ly1.svg'
|
||||
import router from '@/router'
|
||||
import Item from '../components/Item.vue'
|
||||
import { useAddressPageContext } from '../composables/useAddressPage'
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="bg-white py-3 px-3 text-black font-bold drop-shadow z-[1999] wrap-fix">کارت هدیه</div>
|
||||
<div class="px-5 pt-26 pb-2">
|
||||
<MobilePageHeader title="کارت هدیه" />
|
||||
<div class="px-5 mpage-content pb-2">
|
||||
<div class="border-gray-200 border-1 rounded-xl p-5 trans h-40 relative">
|
||||
<bg_main />
|
||||
<div class="z-100 relative">
|
||||
@@ -14,5 +14,6 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Bg_main from '@/components/bg_main.vue'
|
||||
import MobilePageHeader from '@/layouts/components/MobilePageHeader.vue'
|
||||
import GiftCardForm from '../components/GiftCardForm.vue'
|
||||
</script>
|
||||
|
||||
@@ -9,24 +9,11 @@
|
||||
<div class="text-l mr-2 font-bold">{{ usePriceWithoutType(countBasedInventory, false) }} عدد</div>
|
||||
</div>
|
||||
|
||||
<template v-if="boursecuAssetRows.length">
|
||||
<div
|
||||
v-for="(row, index) in boursecuAssetRows"
|
||||
:key="row.id"
|
||||
:class="[rowClass, index > 0 ? 'mt-2' : '']"
|
||||
class="flex justify-center w-full z-20"
|
||||
>
|
||||
{{ row.label }}:
|
||||
<div class="text-l mr-2 font-bold">{{ row.value }}</div>
|
||||
<BasicIcon
|
||||
v-if="row.info !== 'none'"
|
||||
icon="vuesax-linear:info-circle"
|
||||
size="18"
|
||||
:color="iconColor"
|
||||
@click="handleRowInfo(row)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<BoursecuAssetsSummary
|
||||
v-if="boursecuLayout"
|
||||
variant="mobile"
|
||||
icon-color="primary"
|
||||
/>
|
||||
|
||||
<template v-else-if="meta?.single_metal && primaryMetalTotal">
|
||||
<div :class="[rowClass, meta?.has_count_based_products ? 'mt-2' : '']" class="flex justify-center w-full z-20">
|
||||
@@ -87,14 +74,11 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import BoursecuAssetsSummary from './BoursecuAssetsSummary.vue'
|
||||
import { usePriceWithoutType } from '@/utils/usePrice'
|
||||
import { useWalletAssets } from '@/composables/useWalletAssets'
|
||||
import type { WalletMetalTotal } from '@/models/walletAssets'
|
||||
import {
|
||||
buildBoursecuHomeAssetRows,
|
||||
resolveSummaryMetalWeight,
|
||||
type HomeAssetRow,
|
||||
} from '@/utils/productAssets'
|
||||
import { resolveSummaryMetalWeight } from '@/utils/productAssets'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -104,7 +88,7 @@ const props = withDefaults(
|
||||
{ plain: false, boursecuLayout: false }
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
defineEmits<{
|
||||
openGoldModal: [metal: WalletMetalTotal, modalText?: string, modalTitle?: string, modalContent?: string]
|
||||
openRialModal: []
|
||||
}>()
|
||||
@@ -127,9 +111,6 @@ const rootClass = computed(() => {
|
||||
const meta = computed(() => assetsSummary.value?.meta)
|
||||
const metalTotals = computed(() => assetsSummary.value?.metal_totals ?? [])
|
||||
const primaryMetalTotal = computed(() => metalTotals.value[0])
|
||||
const boursecuAssetRows = computed(() =>
|
||||
props.boursecuLayout ? buildBoursecuHomeAssetRows(assetsSummary.value) : []
|
||||
)
|
||||
const cashWallet = computed(() => assetsSummary.value?.cash_wallet ?? 0)
|
||||
const inventoryCards = computed(() => assetsSummary.value?.inventory_cards ?? [])
|
||||
const countBasedInventory = computed(() =>
|
||||
@@ -139,24 +120,10 @@ const countBasedInventory = computed(() =>
|
||||
function formatMetalTotal(metal: WalletMetalTotal) {
|
||||
return `${usePriceWithoutType(resolveSummaryMetalWeight(metal, inventoryCards.value), false)}${metal.unit}`
|
||||
}
|
||||
|
||||
function handleRowInfo(row: HomeAssetRow) {
|
||||
if (row.info === 'rial') {
|
||||
emit('openRialModal')
|
||||
return
|
||||
}
|
||||
|
||||
if (row.metal) {
|
||||
emit('openGoldModal', row.metal, row.modalText, row.modalTitle, row.modalContent)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@boursecu-surface-border: #d4e3de;
|
||||
|
||||
.assets-section--boursecu {
|
||||
background-color: #eff5f3;
|
||||
border: 2px solid @boursecu-surface-border;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<div ref="rootRef" class="boursecu-assets" :class="`boursecu-assets--${variant}`">
|
||||
<div
|
||||
v-for="(row, index) in rows"
|
||||
:key="row.id"
|
||||
:class="['boursecu-assets__row', index > 0 ? 'boursecu-assets__row--spaced' : '']"
|
||||
>
|
||||
<span class="boursecu-assets__label">{{ row.label }}:</span>
|
||||
<strong class="boursecu-assets__value">{{ row.value }}</strong>
|
||||
<div class="boursecu-assets__info-wrap">
|
||||
<button
|
||||
type="button"
|
||||
class="boursecu-assets__info-btn"
|
||||
:aria-expanded="openRowId === row.id"
|
||||
aria-label="اطلاعات بیشتر"
|
||||
@click.stop="toggleRow(row.id)"
|
||||
>
|
||||
<BasicIcon icon="vuesax-linear:info-circle" size="18" :color="iconColor" />
|
||||
</button>
|
||||
<div
|
||||
v-if="openRowId === row.id"
|
||||
class="boursecu-assets__popup"
|
||||
@click.stop
|
||||
>
|
||||
<div
|
||||
v-for="line in row.popupLines"
|
||||
:key="line.label"
|
||||
class="boursecu-assets__popup-line"
|
||||
:class="{
|
||||
'boursecu-assets__popup-line--profit': line.tone === 'profit',
|
||||
'boursecu-assets__popup-line--loss': line.tone === 'loss',
|
||||
}"
|
||||
>
|
||||
<span class="boursecu-assets__popup-label">{{ line.label }}</span>
|
||||
<span class="boursecu-assets__popup-value">{{ line.value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { onClickOutside } from '@vueuse/core'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import { useWalletAssets } from '@/composables/useWalletAssets'
|
||||
import {
|
||||
buildBoursecuHomeAssetRows,
|
||||
type BoursecuHomeAssetRowId,
|
||||
} from '@/utils/productAssets'
|
||||
import { getTradeBuyPrice, lastPrice } from '@/utils/price'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
variant?: 'mobile' | 'desktop'
|
||||
iconColor?: string
|
||||
}>(),
|
||||
{
|
||||
variant: 'mobile',
|
||||
iconColor: 'primary',
|
||||
}
|
||||
)
|
||||
|
||||
const { assetsSummary } = useWalletAssets()
|
||||
const rootRef = ref<HTMLElement | null>(null)
|
||||
const openRowId = ref<BoursecuHomeAssetRowId | null>(null)
|
||||
|
||||
const rows = computed(() =>
|
||||
buildBoursecuHomeAssetRows(assetsSummary.value, getTradeBuyPrice(lastPrice.value))
|
||||
)
|
||||
|
||||
function toggleRow(rowId: BoursecuHomeAssetRowId) {
|
||||
openRowId.value = openRowId.value === rowId ? null : rowId
|
||||
}
|
||||
|
||||
onClickOutside(rootRef, () => {
|
||||
openRowId.value = null
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.boursecu-assets {
|
||||
position: relative;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.boursecu-assets__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
color: var(--app-color-primary, #133050);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.boursecu-assets__row--spaced {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.boursecu-assets__value {
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.boursecu-assets__info-wrap {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.boursecu-assets__info-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.boursecu-assets__popup {
|
||||
position: absolute;
|
||||
top: calc(100% + 8px);
|
||||
right: 0;
|
||||
z-index: 40;
|
||||
min-width: min(18rem, 78vw);
|
||||
padding: 10px 12px;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
border: 1px solid #d4e3de;
|
||||
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
|
||||
.boursecu-assets__popup-line {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 6px 0;
|
||||
font-size: 0.8125rem;
|
||||
color: #374151;
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid #eef2f1;
|
||||
}
|
||||
}
|
||||
|
||||
.boursecu-assets__popup-label {
|
||||
flex: 1;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.boursecu-assets__popup-value {
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.boursecu-assets__popup-line--profit .boursecu-assets__popup-value {
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.boursecu-assets__popup-line--loss .boursecu-assets__popup-value {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.boursecu-assets--desktop {
|
||||
margin-bottom: 16px;
|
||||
padding: 16px 18px;
|
||||
border-radius: 12px;
|
||||
background-color: #eff5f3;
|
||||
|
||||
.boursecu-assets__row {
|
||||
justify-content: flex-start;
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
|
||||
.boursecu-assets__popup {
|
||||
right: auto;
|
||||
left: 0;
|
||||
min-width: 20rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,10 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import BoursecuAssetsSummary from '../../components/BoursecuAssetsSummary.vue'
|
||||
import { usePriceWithoutType } from '@/utils/usePrice'
|
||||
import { useWalletAssets } from '@/composables/useWalletAssets'
|
||||
import type { WalletMetalTotal } from '@/models/walletAssets'
|
||||
import { buildBoursecuHomeAssetRows, resolveSummaryMetalWeight } from '@/utils/productAssets'
|
||||
import { resolveSummaryMetalWeight } from '@/utils/productAssets'
|
||||
import { useHomePageContext } from '../../composables/useHomePage'
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -29,9 +30,6 @@ type MetricItem = {
|
||||
label: string
|
||||
value: string
|
||||
metal?: WalletMetalTotal
|
||||
modalText?: string
|
||||
modalTitle?: string
|
||||
modalContent?: string
|
||||
showInfo?: boolean
|
||||
onInfo?: 'gold' | 'rial'
|
||||
}
|
||||
@@ -47,23 +45,6 @@ const metrics = computed<MetricItem[]>(() => {
|
||||
})
|
||||
}
|
||||
|
||||
if (isBoursecuLayout.value) {
|
||||
buildBoursecuHomeAssetRows(assetsSummary.value).forEach((row) => {
|
||||
items.push({
|
||||
id: row.id,
|
||||
label: row.label,
|
||||
value: row.value,
|
||||
metal: row.metal,
|
||||
modalText: row.modalText,
|
||||
modalTitle: row.modalTitle,
|
||||
modalContent: row.modalContent,
|
||||
showInfo: row.info !== 'none',
|
||||
onInfo: row.info === 'rial' ? 'rial' : 'gold',
|
||||
})
|
||||
})
|
||||
return items
|
||||
}
|
||||
|
||||
if (meta.value?.single_metal && metalTotals.value[0]) {
|
||||
const metal = metalTotals.value[0]
|
||||
items.push({
|
||||
@@ -108,13 +89,19 @@ function handleInfo(metric: MetricItem) {
|
||||
return
|
||||
}
|
||||
if (metric.metal) {
|
||||
emit('openGoldModal', metric.metal, metric.modalText, metric.modalTitle, metric.modalContent)
|
||||
emit('openGoldModal', metric.metal)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dhome-metrics">
|
||||
<BoursecuAssetsSummary
|
||||
v-if="isBoursecuLayout"
|
||||
variant="desktop"
|
||||
icon-color="tertiary"
|
||||
/>
|
||||
|
||||
<div v-else class="dhome-metrics">
|
||||
<article v-for="metric in metrics" :key="metric.id" class="dhome-metric-tile">
|
||||
<div class="dhome-metric-tile__head">
|
||||
<span class="dhome-metric-tile__label">{{ metric.label }}</span>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<MobileNewRequestView v-show="!isDesktop" />
|
||||
<DesktopNewRequestView v-show="isDesktop" />
|
||||
<MobileNewRequestView v-if="!isDesktop" />
|
||||
<DesktopNewRequestView v-if="isDesktop" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
@@ -30,22 +30,28 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||
import type { Address } from '@/models/address'
|
||||
|
||||
interface Props {
|
||||
addresses?: Address[]
|
||||
variant?: 'mobile' | 'desktop'
|
||||
selectedAddressId?: number
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
variant: 'mobile',
|
||||
})
|
||||
|
||||
const selectedAddressID = defineModel<number | undefined>('selectedAddressID')
|
||||
|
||||
const emit = defineEmits<{
|
||||
addAddress: []
|
||||
submit: []
|
||||
'update:selectedAddressId': [value: number | undefined]
|
||||
}>()
|
||||
|
||||
const selectedAddressID = computed({
|
||||
get: () => props.selectedAddressId,
|
||||
set: (value: number | undefined) => emit('update:selectedAddressId', value),
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { inject, onMounted, provide, ref, type InjectionKey, type Ref } from 'vue'
|
||||
import { inject, nextTick, onMounted, provide, ref, type InjectionKey, type Ref } from 'vue'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { message } from 'ant-design-vue'
|
||||
import router from '@/router'
|
||||
import { getProductInventory } from '@/api/products'
|
||||
import { physicalReceiveApi } from '@/api/physicalreceive'
|
||||
import { tableAddress } from '@/api/address'
|
||||
@@ -93,6 +94,11 @@ export function useNewPhysicalRequestPage() {
|
||||
}
|
||||
|
||||
async function handelsubmit() {
|
||||
if (!selectedAddressID.value) {
|
||||
message.error('لطفاً آدرس را انتخاب کنید.')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
productsByWalletId.value = selectedProducts.value.map((val) => ({
|
||||
@@ -103,14 +109,19 @@ export function useNewPhysicalRequestPage() {
|
||||
address_id: selectedAddressID.value,
|
||||
wallets: productsByWalletId.value,
|
||||
})
|
||||
await initProductWallet()
|
||||
message.success('درخواست شما با موفقیت ارسال شد')
|
||||
|
||||
showAddress.value = false
|
||||
selectedAddressID.value = undefined
|
||||
selectedProducts.value = []
|
||||
await nextTick()
|
||||
|
||||
message.success('درخواست شما با موفقیت ارسال شد')
|
||||
await initProductWallet()
|
||||
await router.push({ name: 'list' })
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
showAddress.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
destroy-on-close
|
||||
custom-footer
|
||||
>
|
||||
<New_EditAddress @onOk="handleAddress" />
|
||||
<New_EditAddress v-if="addAddressVisible" :visible="addAddressVisible" @onOk="handleAddress" />
|
||||
<template #footer>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<div ref="el" class="px-3 pt-15">
|
||||
<div ref="el" class="px-3 mpage-content pb-2">
|
||||
<div class="flex gap-2">
|
||||
<BasicSelect
|
||||
v-model:value="selectedProduct"
|
||||
@@ -45,7 +45,7 @@
|
||||
</BasicActionSheet>
|
||||
|
||||
<BasicActionSheet v-model:show="addAddressVisible">
|
||||
<New_EditAddress @onOk="handleAddress" />
|
||||
<New_EditAddress v-if="addAddressVisible" :visible="addAddressVisible" @onOk="handleAddress" />
|
||||
</BasicActionSheet>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
<template>
|
||||
<div class="physical-receive-mobile-chrome" style="max-height: calc(100vh - 50px)">
|
||||
<div class="bg-white py-3 px-3 z-100000 text-primary font-bold drop-shadow flex flex-col wrap-fix">
|
||||
<div class="flex">
|
||||
<div @click="router.back()">
|
||||
<BasicIcon icon="vuesax-linear:arrow-right-1" />
|
||||
</div>
|
||||
<div v-if="route.name === 'list'" class="mr-2">دریافت فیزیکی</div>
|
||||
<div v-else class="mr-2">دریافت فیزیکی-درخواست جدید</div>
|
||||
</div>
|
||||
</div>
|
||||
<MobilePageHeader :title="pageTitle" show-back />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import router from '@/router'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import MobilePageHeader from '@/layouts/components/MobilePageHeader.vue'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const pageTitle = computed(() =>
|
||||
route.name === 'list' ? 'دریافت فیزیکی' : 'دریافت فیزیکی-درخواست جدید',
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="flex flex-col gap-4 pt-13 px-2 pb-[calc(var(--app-bottom-chrome-height)+var(--app-add-card-height)+1rem)]"
|
||||
class="flex flex-col gap-4 mpage-content px-2 pb-[calc(var(--app-bottom-chrome-height)+var(--app-add-card-height)+1rem)]"
|
||||
>
|
||||
<RequestItem v-for="item in requestItems" :key="item.id" :item="item" />
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="bg-white py-3 px-3 text-primary font-bold drop-shadow wrap-fix flex z-10">
|
||||
<div @click="router.back()">
|
||||
<BasicIcon icon="vuesax-linear:arrow-right-1" />
|
||||
</div>
|
||||
<div class="mr-2">هشدارهای فعلی</div>
|
||||
</div>
|
||||
<MobilePageHeader title="هشدارهای فعلی" show-back />
|
||||
|
||||
<div
|
||||
:class="loading ? 'h-screen' : ''"
|
||||
class="px-3 pt-16 pb-2 overflow-y-scroll"
|
||||
style="max-height: calc(100vh - 50px)"
|
||||
class="px-3 mpage-content pb-2 overflow-y-scroll mpage-scroll"
|
||||
>
|
||||
<list-anim v-if="!loading" class="pb-[calc(var(--app-bottom-chrome-height)+var(--app-add-card-height)+1rem)]">
|
||||
<Item
|
||||
@@ -41,9 +35,9 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import MobilePageHeader from '@/layouts/components/MobilePageHeader.vue'
|
||||
import ListAnim from '@/components/ListAnim.vue'
|
||||
import TransactionSkeleton from '@/components/TransactionSkeleton.vue'
|
||||
import router from '@/router'
|
||||
import Item from '../components/Item.vue'
|
||||
import { usePriceTriggerPageContext } from '../composables/usePriceTriggerPage'
|
||||
|
||||
|
||||
@@ -4,16 +4,9 @@
|
||||
<div class="bg-gray-100 m-2 rounded-lg w-28 h-28 flex justify-center p-2">
|
||||
<img :src="displayImage" class="object-contain w-full h-full" alt="">
|
||||
</div>
|
||||
<div class="m-1 flex flex-col gap-1 justify-center">
|
||||
<div class="m-1 flex flex-col gap-0 justify-center leading-tight">
|
||||
<div class="text-base font-500">{{ props.ProductItem.title }}</div>
|
||||
<br>
|
||||
<!-- <div v-if="props.ProductItem.group_type!='آبشده'" class=" font-500">{{ amountProduct}}</div>-->
|
||||
<!-- <div v-if="props.ProductItem.group_type!='آبشده'" class="text-secondary font-500">{{ props.ProductItem.amount }}گرم</div>-->
|
||||
<!-- <div v-else class="text-secondary font-500">حداقل خرید یک گرم</div>-->
|
||||
<!-- <div>{{usePrice((props.ProductItem.price ?? 0) + (props.ProductItem.cash_fee_buy ?? 0))}}</div>-->
|
||||
<div>{{ usePrice(getProductBuyPrice(props.ProductItem)) }}</div>
|
||||
<!-- <div v-if="props.ProductItem.group_type!='آبشده'" class="text-xs text-gray-400">شامل 33 هزار تومان اجرت</div>-->
|
||||
<!-- <div v-else class="text-xs text-gray-400">محاسبه بر اساس قیمت روز</div>-->
|
||||
<div class="text-base font-500">{{ usePrice(getProductBuyPrice(props.ProductItem)) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div class="dproducts">
|
||||
<div class="dproducts__container">
|
||||
<header class="dproducts-topbar">
|
||||
<p class="dproducts-topbar__eyebrow">معاملات</p>
|
||||
<h1 class="dproducts-topbar__title">سفارشها</h1>
|
||||
<p class="dproducts-topbar__subtitle">محصول مورد نظر را انتخاب کنید و سفارش خود را ثبت کنید</p>
|
||||
</header>
|
||||
<DesktopPageTopbar
|
||||
eyebrow="معاملات"
|
||||
title="خرید و فروش"
|
||||
subtitle="محصول مورد نظر را انتخاب کنید و سفارش خود را ثبت کنید"
|
||||
/>
|
||||
|
||||
<div class="dproducts-tabs">
|
||||
<button
|
||||
@@ -47,6 +47,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import DesktopPageTopbar from '@/layouts/components/DesktopPageTopbar.vue'
|
||||
import DesktopProductCard from './components/DesktopProductCard.vue'
|
||||
import { useProductsPageContext } from '../composables/useProductsPage'
|
||||
|
||||
|
||||
@@ -12,34 +12,6 @@
|
||||
padding: 28px 36px 40px;
|
||||
}
|
||||
|
||||
.dproducts-topbar {
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 24px;
|
||||
border-bottom: 1px solid rgba(15, 23, 42, 0.06);
|
||||
|
||||
&__eyebrow {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #9ca3af;
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.03em;
|
||||
color: #111827;
|
||||
margin: 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
margin: 8px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dproducts-tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
<div class="bottom-0 fixed top-0">
|
||||
<img :src="img" alt="" class="w-inherit" :style="{ backgroundRepeat: 'repeat' }" />
|
||||
</div>
|
||||
<MobilePageHeader title="خرید و فروش" />
|
||||
<a-spin :spinning="loading">
|
||||
<div class="!h-full overflow-y-scroll">
|
||||
<div class="!h-full overflow-y-scroll mpage-content mpage-scroll">
|
||||
<div class="w-full flex justify-center top-0 py-5">
|
||||
<ARadioGroup
|
||||
v-model:value="activekey"
|
||||
@@ -40,6 +41,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import img from '@/assets/img/user_ly1.svg'
|
||||
import MobilePageHeader from '@/layouts/components/MobilePageHeader.vue'
|
||||
import ProductItem from '@/views/base/products/components/ProductItem.vue'
|
||||
import { useProductsPageContext } from '../composables/useProductsPage'
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<template>
|
||||
<div class="boursecu-transactions pb-[calc(var(--app-bottom-chrome-height)+1rem)]">
|
||||
<div class="px-4 pt-4 pb-2">
|
||||
<h1 class="text-xl font-bold text-gray-900">گزارشها</h1>
|
||||
</div>
|
||||
<MobilePageHeader title="تراکنش ها" />
|
||||
|
||||
<div class="mpage-content">
|
||||
<div class="boursecu-transactions__tabs px-4">
|
||||
<div class="flex gap-5 overflow-x-auto whitespace-nowrap pb-1">
|
||||
<button
|
||||
@@ -40,29 +39,43 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-4 mt-4 flex items-center gap-2 flex-wrap">
|
||||
<div class="flex items-center gap-1 text-sm text-gray-600 shrink-0">
|
||||
<div v-if="activeTab.showAdvancedFilters" class="boursecu-transactions__filters px-4 mt-4">
|
||||
<div class="boursecu-transactions__filters-label">
|
||||
<BasicIcon icon="vuesax-linear:filter" size="16" />
|
||||
<span>فیلترها:</span>
|
||||
</div>
|
||||
<a-select
|
||||
v-model:value="statusFilter"
|
||||
allow-clear
|
||||
class="boursecu-transactions__filter-select min-w-[7rem]"
|
||||
class="boursecu-transactions__filter-select"
|
||||
placeholder="وضعیت"
|
||||
size="large"
|
||||
@change="applyInlineFilters"
|
||||
>
|
||||
<a-select-option value="active">فعال</a-select-option>
|
||||
<a-select-option value="inactive">غیرفعال</a-select-option>
|
||||
<a-select-option
|
||||
v-for="option in statusOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<BaseDatePicker
|
||||
v-model:value="dateFilter"
|
||||
class="boursecu-transactions__filter-date min-w-[7rem]"
|
||||
placeholder="تاریخ"
|
||||
size="large"
|
||||
@onchange="applyInlineFilters"
|
||||
/>
|
||||
<div class="boursecu-transactions__filter-dates">
|
||||
<BaseDatePicker
|
||||
v-model:value="dateFromFilter"
|
||||
class="boursecu-transactions__filter-date"
|
||||
placeholder="تاریخ از"
|
||||
size="large"
|
||||
@onchange="applyInlineFilters"
|
||||
/>
|
||||
<BaseDatePicker
|
||||
v-model:value="dateToFilter"
|
||||
class="boursecu-transactions__filter-date"
|
||||
placeholder="تاریخ تا"
|
||||
size="large"
|
||||
@onchange="applyInlineFilters"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ref="listRef" :class="loading ? 'min-h-[50vh]' : ''" class="px-4 mt-5">
|
||||
@@ -86,6 +99,7 @@
|
||||
<p class="boursecu-transactions__empty-text">{{ emptyText }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -93,6 +107,7 @@
|
||||
import { ref } from 'vue'
|
||||
import { loading, loadingMore } from '@/utils'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import MobilePageHeader from '@/layouts/components/MobilePageHeader.vue'
|
||||
import BaseDatePicker from '@/components/BasicPacks/BaseDatePicker/src/BaseDatePicker.vue'
|
||||
import ListAnim from '@/components/ListAnim.vue'
|
||||
import TransactionSkeleton from '@/components/TransactionSkeleton.vue'
|
||||
@@ -105,8 +120,10 @@ const listRef = ref<HTMLElement | null>(null)
|
||||
|
||||
const {
|
||||
items,
|
||||
dateFilter,
|
||||
dateFromFilter,
|
||||
dateToFilter,
|
||||
statusFilter,
|
||||
statusOptions,
|
||||
activeTabId,
|
||||
tradeSide,
|
||||
activeTab,
|
||||
@@ -171,10 +188,33 @@ const {
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.boursecu-transactions__filter-select,
|
||||
.boursecu-transactions__filters {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.boursecu-transactions__filters-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.boursecu-transactions__filter-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.boursecu-transactions__filter-dates {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.boursecu-transactions__filter-date {
|
||||
flex: 1;
|
||||
max-width: 9rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.boursecu-transactions__empty {
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="absolute inset-0 bg-cover bg-center opacity-60"></div>
|
||||
<div
|
||||
class="bg-white w-full transaction-list py-3 px-3 font-bold drop-shadow flex justify-between z-10"
|
||||
>
|
||||
<div>لیست تراکنش ها</div>
|
||||
<BasicIcon icon="vuesax-linear:filter" @click="showFilter = true" />
|
||||
</div>
|
||||
<MobilePageHeader title="تراکنش ها">
|
||||
<template #trailing>
|
||||
<BasicIcon icon="vuesax-linear:filter" @click="showFilter = true" />
|
||||
</template>
|
||||
</MobilePageHeader>
|
||||
|
||||
<div ref="el" :class="loading ? 'h-screen' : ''" class="px-3 pt-16 pb-10">
|
||||
<div ref="el" :class="loading ? 'h-screen' : ''" class="px-3 mpage-content pb-10">
|
||||
<list-anim v-if="!loading">
|
||||
<Item v-for="item in items" :key="item.id" :item="item" />
|
||||
<div :ref="bindMobileObserver" v-show="!loading" class="w-full opacity-0 relative">
|
||||
@@ -45,6 +44,7 @@ import { ref } from 'vue'
|
||||
import { loading, loadingMore } from '@/utils'
|
||||
import Item from './components/Item.vue'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import MobilePageHeader from '@/layouts/components/MobilePageHeader.vue'
|
||||
import BasicActionSheet from '@/components/BasicPacks/BasicActionSheet.vue'
|
||||
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||
import BaseDatePicker from '@/components/BasicPacks/BaseDatePicker/src/BaseDatePicker.vue'
|
||||
@@ -64,16 +64,3 @@ const {
|
||||
bindMobileObserver,
|
||||
} = useTransactionsPageContext()
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.transaction-list {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
left: 0;
|
||||
@media screen and (min-width: 426px) {
|
||||
width: 426px;
|
||||
left: auto;
|
||||
right: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,6 +4,8 @@ import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
|
||||
import { loading, loadingMore } from '@/utils'
|
||||
import {
|
||||
BOURSECU_TRANSACTION_TABS,
|
||||
getBoursecuStatusFilterGroup,
|
||||
getBoursecuStatusOptionsForTab,
|
||||
type BoursecuTradeSide,
|
||||
type BoursecuTransactionTab,
|
||||
type BoursecuTransactionTabId,
|
||||
@@ -16,6 +18,8 @@ import {
|
||||
resetTransactionsPagination,
|
||||
transactionActiveTabId,
|
||||
transactionDateFilter,
|
||||
transactionDateFromFilter,
|
||||
transactionDateToFilter,
|
||||
transactionItems,
|
||||
transactionPagination,
|
||||
transactionStatusFilter,
|
||||
@@ -30,12 +34,15 @@ export interface TransactionsPageContext {
|
||||
tableOutput: typeof transactionTableOutput
|
||||
showFilter: ReturnType<typeof ref<boolean>>
|
||||
dateFilter: typeof transactionDateFilter
|
||||
dateFromFilter: typeof transactionDateFromFilter
|
||||
dateToFilter: typeof transactionDateToFilter
|
||||
statusFilter: typeof transactionStatusFilter
|
||||
mobileObserverEl: ReturnType<typeof ref<HTMLElement | null>>
|
||||
desktopObserverEl: ReturnType<typeof ref<HTMLElement | null>>
|
||||
activeTabId: typeof transactionActiveTabId
|
||||
tradeSide: typeof transactionTradeSide
|
||||
activeTab: ReturnType<typeof computed<BoursecuTransactionTab>>
|
||||
statusOptions: ReturnType<typeof computed<ReturnType<typeof getBoursecuStatusOptionsForTab>>>
|
||||
emptyText: ReturnType<typeof computed<string>>
|
||||
resetPagination: typeof resetTransactionsPagination
|
||||
initTable: () => Promise<void>
|
||||
@@ -68,6 +75,8 @@ export function useTransactionsPageProvider(options?: { boursecu?: boolean }) {
|
||||
BOURSECU_TRANSACTION_TABS[0],
|
||||
)
|
||||
|
||||
const statusOptions = computed(() => getBoursecuStatusOptionsForTab(activeTab.value))
|
||||
|
||||
const emptyText = computed(() => {
|
||||
if (!transactionsBoursecuMode.value) {
|
||||
return 'تراکنشی موجود نیست'
|
||||
@@ -94,6 +103,14 @@ export function useTransactionsPageProvider(options?: { boursecu?: boolean }) {
|
||||
return
|
||||
}
|
||||
|
||||
const currentTab = activeTab.value
|
||||
const nextTab =
|
||||
BOURSECU_TRANSACTION_TABS.find((tab) => tab.id === tabId) ?? BOURSECU_TRANSACTION_TABS[0]
|
||||
|
||||
if (getBoursecuStatusFilterGroup(currentTab) !== getBoursecuStatusFilterGroup(nextTab)) {
|
||||
transactionStatusFilter.value = undefined
|
||||
}
|
||||
|
||||
transactionActiveTabId.value = tabId
|
||||
await reloadTransactionsTable()
|
||||
}
|
||||
@@ -152,12 +169,15 @@ export function useTransactionsPageProvider(options?: { boursecu?: boolean }) {
|
||||
tableOutput: transactionTableOutput,
|
||||
showFilter,
|
||||
dateFilter: transactionDateFilter,
|
||||
dateFromFilter: transactionDateFromFilter,
|
||||
dateToFilter: transactionDateToFilter,
|
||||
statusFilter: transactionStatusFilter,
|
||||
mobileObserverEl,
|
||||
desktopObserverEl,
|
||||
activeTabId: transactionActiveTabId,
|
||||
tradeSide: transactionTradeSide,
|
||||
activeTab,
|
||||
statusOptions,
|
||||
emptyText,
|
||||
resetPagination: resetTransactionsPagination,
|
||||
initTable,
|
||||
|
||||
@@ -20,6 +20,8 @@ export const transactionPagination = ref<TableEngineInput>({ filters: [], limit:
|
||||
export const transactionItems = ref<Transaction[]>([])
|
||||
export const transactionTableOutput = ref<TableEngineOutput<Transaction> | null>(null)
|
||||
export const transactionDateFilter = ref<string>()
|
||||
export const transactionDateFromFilter = ref<string>()
|
||||
export const transactionDateToFilter = ref<string>()
|
||||
export const transactionStatusFilter = ref<string>()
|
||||
export const transactionActiveTabId = ref<BoursecuTransactionTabId>('trades')
|
||||
export const transactionTradeSide = ref<BoursecuTradeSide>('buy')
|
||||
@@ -40,7 +42,8 @@ export function resetTransactionsPagination() {
|
||||
transactionPagination.value = buildBoursecuTransactionPagination(
|
||||
getActiveBoursecuTab(),
|
||||
transactionTradeSide.value,
|
||||
transactionDateFilter.value,
|
||||
transactionDateFromFilter.value,
|
||||
transactionDateToFilter.value,
|
||||
transactionStatusFilter.value,
|
||||
)
|
||||
return
|
||||
@@ -151,6 +154,8 @@ export function resetTransactionsPageData() {
|
||||
transactionItems.value = []
|
||||
transactionTableOutput.value = null
|
||||
transactionDateFilter.value = undefined
|
||||
transactionDateFromFilter.value = undefined
|
||||
transactionDateToFilter.value = undefined
|
||||
transactionStatusFilter.value = undefined
|
||||
transactionActiveTabId.value = 'trades'
|
||||
transactionTradeSide.value = 'buy'
|
||||
|
||||
@@ -10,21 +10,50 @@ export type BoursecuTradeSide = 'buy' | 'sell'
|
||||
|
||||
export type BoursecuTransactionFetchMode = 'transaction' | 'deposit'
|
||||
|
||||
export type BoursecuTradeStatusFilter = 'completed' | 'pending' | 'cancelled'
|
||||
export type BoursecuFulfillmentStatusFilter = 'delivered' | 'registered' | 'cancelled'
|
||||
export type BoursecuTransactionStatusFilter = BoursecuTradeStatusFilter | BoursecuFulfillmentStatusFilter
|
||||
|
||||
export type BoursecuStatusFilterGroup = 'trade' | 'fulfillment'
|
||||
|
||||
export interface BoursecuTransactionStatusOption {
|
||||
value: string
|
||||
label: string
|
||||
sts: number
|
||||
}
|
||||
|
||||
export interface BoursecuTransactionTab {
|
||||
id: BoursecuTransactionTabId
|
||||
label: string
|
||||
emptyText: string
|
||||
showTradeToggle?: boolean
|
||||
showAdvancedFilters?: boolean
|
||||
fetchMode?: BoursecuTransactionFetchMode
|
||||
typeFilters?: number[]
|
||||
}
|
||||
|
||||
export const BOURSECU_TRADE_STATUS_OPTIONS: BoursecuTransactionStatusOption[] = [
|
||||
{ value: 'completed', label: 'تکمیل شده', sts: 2 },
|
||||
{ value: 'pending', label: 'در انتظار تایید', sts: 1 },
|
||||
{ value: 'cancelled', label: 'لغو شده', sts: 3 },
|
||||
]
|
||||
|
||||
export const BOURSECU_FULFILLMENT_STATUS_OPTIONS: BoursecuTransactionStatusOption[] = [
|
||||
{ value: 'delivered', label: 'تحویل شده', sts: 2 },
|
||||
{ value: 'registered', label: 'ثبت درخواست', sts: 1 },
|
||||
{ value: 'cancelled', label: 'لغو شده', sts: 3 },
|
||||
]
|
||||
|
||||
/** @deprecated Use getBoursecuStatusOptionsForTab instead */
|
||||
export const BOURSECU_TRANSACTION_STATUS_OPTIONS = BOURSECU_TRADE_STATUS_OPTIONS
|
||||
|
||||
export const BOURSECU_TRANSACTION_TABS: BoursecuTransactionTab[] = [
|
||||
{
|
||||
id: 'trades',
|
||||
label: 'همه',
|
||||
emptyText: 'معاملهای برای شما به ثبت نرسیده است',
|
||||
showTradeToggle: true,
|
||||
showAdvancedFilters: true,
|
||||
typeFilters: [1, 2],
|
||||
},
|
||||
{
|
||||
@@ -32,17 +61,20 @@ export const BOURSECU_TRANSACTION_TABS: BoursecuTransactionTab[] = [
|
||||
label: 'واریز',
|
||||
emptyText: 'واریزی برای شما به ثبت نرسیده است',
|
||||
fetchMode: 'deposit',
|
||||
showAdvancedFilters: true,
|
||||
},
|
||||
{
|
||||
id: 'withdrawal',
|
||||
label: 'برداشت',
|
||||
emptyText: 'برداشتی برای شما به ثبت نرسیده است',
|
||||
showAdvancedFilters: true,
|
||||
typeFilters: [4],
|
||||
},
|
||||
{
|
||||
id: 'physical',
|
||||
label: 'تحویل فیزیکی',
|
||||
emptyText: 'درخواست تحویل فیزیکی برای شما ثبت نشده است',
|
||||
showAdvancedFilters: true,
|
||||
typeFilters: [9],
|
||||
},
|
||||
]
|
||||
@@ -52,11 +84,43 @@ const TRADE_TYPE_BY_SIDE: Record<BoursecuTradeSide, number> = {
|
||||
sell: 2,
|
||||
}
|
||||
|
||||
export function getBoursecuStatusFilterGroup(tab: BoursecuTransactionTab): BoursecuStatusFilterGroup {
|
||||
return tab.id === 'withdrawal' || tab.id === 'physical' ? 'fulfillment' : 'trade'
|
||||
}
|
||||
|
||||
export function getBoursecuStatusOptionsForTab(
|
||||
tab: BoursecuTransactionTab,
|
||||
): BoursecuTransactionStatusOption[] {
|
||||
return getBoursecuStatusFilterGroup(tab) === 'fulfillment'
|
||||
? BOURSECU_FULFILLMENT_STATUS_OPTIONS
|
||||
: BOURSECU_TRADE_STATUS_OPTIONS
|
||||
}
|
||||
|
||||
function getBoursecuStatusFilterField(tab: BoursecuTransactionTab): string {
|
||||
if (tab.fetchMode === 'deposit') {
|
||||
return 'sts'
|
||||
}
|
||||
|
||||
if (tab.id === 'withdrawal') {
|
||||
return 'withdraw.sts'
|
||||
}
|
||||
|
||||
return 'order.sts'
|
||||
}
|
||||
|
||||
function resolveBoursecuStatusSts(
|
||||
tab: BoursecuTransactionTab,
|
||||
statusFilter?: BoursecuTransactionStatusFilter | string,
|
||||
): number | undefined {
|
||||
return getBoursecuStatusOptionsForTab(tab).find((option) => option.value === statusFilter)?.sts
|
||||
}
|
||||
|
||||
export function buildBoursecuTransactionPagination(
|
||||
tab: BoursecuTransactionTab,
|
||||
tradeSide: BoursecuTradeSide,
|
||||
dateFilter?: string,
|
||||
statusFilter?: string
|
||||
dateFrom?: string,
|
||||
dateTo?: string,
|
||||
statusFilter?: BoursecuTransactionStatusFilter | string,
|
||||
): TableEngineInput {
|
||||
const filters: TableEngineInput['filters'] = []
|
||||
|
||||
@@ -75,24 +139,37 @@ export function buildBoursecuTransactionPagination(
|
||||
})
|
||||
}
|
||||
|
||||
if (dateFilter) {
|
||||
filters.push({
|
||||
field: 'created_at',
|
||||
cr: 'and',
|
||||
type: 'date',
|
||||
op: 'eq',
|
||||
value: dateFilter,
|
||||
})
|
||||
}
|
||||
if (tab.showAdvancedFilters) {
|
||||
if (dateFrom) {
|
||||
filters.push({
|
||||
field: 'created_at',
|
||||
cr: 'and',
|
||||
type: 'date',
|
||||
op: 'gte',
|
||||
value: dateFrom,
|
||||
})
|
||||
}
|
||||
|
||||
if (statusFilter === 'active' || statusFilter === 'inactive') {
|
||||
filters.push({
|
||||
field: 'active',
|
||||
cr: 'and',
|
||||
type: 'boolean',
|
||||
op: 'eq',
|
||||
value: statusFilter === 'active',
|
||||
})
|
||||
if (dateTo) {
|
||||
filters.push({
|
||||
field: 'created_at',
|
||||
cr: 'and',
|
||||
type: 'date',
|
||||
op: 'lte',
|
||||
value: dateTo,
|
||||
})
|
||||
}
|
||||
|
||||
const statusSts = resolveBoursecuStatusSts(tab, statusFilter)
|
||||
if (statusSts != null) {
|
||||
filters.push({
|
||||
field: getBoursecuStatusFilterField(tab),
|
||||
cr: 'and',
|
||||
type: 'number',
|
||||
op: 'eq',
|
||||
value: statusSts,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
<template>
|
||||
<div class="dtxn">
|
||||
<div class="dtxn__container">
|
||||
<header class="dtxn-topbar">
|
||||
<p class="dtxn-topbar__eyebrow">گزارشها</p>
|
||||
<h1 class="dtxn-topbar__title">تراکنشها</h1>
|
||||
</header>
|
||||
<DesktopPageTopbar title="تراکنشها" />
|
||||
|
||||
<div class="dtxn-toolbar">
|
||||
<div class="dtxn-tabs">
|
||||
@@ -40,7 +37,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dtxn-filters">
|
||||
<div v-if="activeTab.showAdvancedFilters" class="dtxn-filters">
|
||||
<div class="dtxn-filters__label">
|
||||
<BasicIcon icon="vuesax-linear:filter" size="16" />
|
||||
<span>فیلترها:</span>
|
||||
@@ -53,16 +50,30 @@
|
||||
size="large"
|
||||
@change="applyInlineFilters"
|
||||
>
|
||||
<a-select-option value="active">فعال</a-select-option>
|
||||
<a-select-option value="inactive">غیرفعال</a-select-option>
|
||||
<a-select-option
|
||||
v-for="option in statusOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<BaseDatePicker
|
||||
v-model:value="dateFilter"
|
||||
class="dtxn-filters__date"
|
||||
placeholder="تاریخ"
|
||||
size="large"
|
||||
@onchange="applyInlineFilters"
|
||||
/>
|
||||
<div class="dtxn-filters__dates">
|
||||
<BaseDatePicker
|
||||
v-model:value="dateFromFilter"
|
||||
class="dtxn-filters__date"
|
||||
placeholder="تاریخ از"
|
||||
size="large"
|
||||
@onchange="applyInlineFilters"
|
||||
/>
|
||||
<BaseDatePicker
|
||||
v-model:value="dateToFilter"
|
||||
class="dtxn-filters__date"
|
||||
placeholder="تاریخ تا"
|
||||
size="large"
|
||||
@onchange="applyInlineFilters"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ref="listRef" :class="loading ? 'min-h-[40vh]' : ''" class="mt-5">
|
||||
@@ -98,6 +109,7 @@
|
||||
import { ref } from 'vue'
|
||||
import { loading, loadingMore } from '@/utils'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import DesktopPageTopbar from '@/layouts/components/DesktopPageTopbar.vue'
|
||||
import BaseDatePicker from '@/components/BasicPacks/BaseDatePicker/src/BaseDatePicker.vue'
|
||||
import ListAnim from '@/components/ListAnim.vue'
|
||||
import TransactionSkeleton from '@/components/TransactionSkeleton.vue'
|
||||
@@ -110,8 +122,10 @@ const listRef = ref<HTMLElement | null>(null)
|
||||
|
||||
const {
|
||||
items,
|
||||
dateFilter,
|
||||
dateFromFilter,
|
||||
dateToFilter,
|
||||
statusFilter,
|
||||
statusOptions,
|
||||
activeTabId,
|
||||
tradeSide,
|
||||
activeTab,
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
<template>
|
||||
<div class="dtxn">
|
||||
<div class="dtxn__container">
|
||||
<header class="dtxn-topbar">
|
||||
<p class="dtxn-topbar__eyebrow">گزارشها</p>
|
||||
<h1 class="dtxn-topbar__title">لیست تراکنشها</h1>
|
||||
</header>
|
||||
<DesktopPageTopbar title="تراکنشها" />
|
||||
|
||||
<div class="dtxn-filters">
|
||||
<div class="dtxn-filters__label">
|
||||
@@ -58,6 +55,7 @@
|
||||
import { ref } from 'vue'
|
||||
import { loading, loadingMore } from '@/utils'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import DesktopPageTopbar from '@/layouts/components/DesktopPageTopbar.vue'
|
||||
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||
import BaseDatePicker from '@/components/BasicPacks/BaseDatePicker/src/BaseDatePicker.vue'
|
||||
import ListAnim from '@/components/ListAnim.vue'
|
||||
|
||||
@@ -12,28 +12,6 @@
|
||||
padding: 28px 36px 40px;
|
||||
}
|
||||
|
||||
.dtxn-topbar {
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 24px;
|
||||
border-bottom: 1px solid rgba(15, 23, 42, 0.06);
|
||||
|
||||
&__eyebrow {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #9ca3af;
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.03em;
|
||||
color: #111827;
|
||||
margin: 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
|
||||
.dtxn-toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -119,6 +97,12 @@
|
||||
&__date {
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
&__dates {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.dtxn-list {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div class="dwallet">
|
||||
<div class="dwallet__container">
|
||||
<header class="dwallet-topbar">
|
||||
<p class="dwallet-topbar__eyebrow">مالی</p>
|
||||
<h1 class="dwallet-topbar__title">کیف پول</h1>
|
||||
<p class="dwallet-topbar__subtitle">واریز و برداشت وجه از کیف پول خود</p>
|
||||
</header>
|
||||
<DesktopPageTopbar
|
||||
eyebrow="مالی"
|
||||
title="کیف پول"
|
||||
subtitle="واریز و برداشت وجه از کیف پول خود"
|
||||
/>
|
||||
|
||||
<div class="dwallet-toolbar">
|
||||
<div class="dwallet-toggle">
|
||||
@@ -159,6 +159,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||
import DesktopPageTopbar from '@/layouts/components/DesktopPageTopbar.vue'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import BasicInput from '@/components/BasicPacks/BasicInput.vue'
|
||||
import ListAnim from '@/components/ListAnim.vue'
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="absolute inset-0 bg-cover bg-center opacity-60" :style="{ backgroundImage: `url(${img})` }"></div>
|
||||
<div class="bg-white py-3 px-3 text-primary font-bold drop-shadow z-[1999] wrap-fix">کیف پول</div>
|
||||
<div class="bg-white wrap-fix py-3 top-11 px-3 text-primary font-bold flex justify-evenly z-[1999]">
|
||||
<MobilePageHeader title="کیف پول" />
|
||||
<div class="mpage-subheader wrap-fix flex justify-evenly">
|
||||
<div class="flex justify-evenly w-full px-2 h-11 items-center bg-gray-50 relative rounded">
|
||||
<div
|
||||
:class="isDeposit ? 'text-primary' : 'text-gray-400'"
|
||||
@@ -25,7 +25,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="px-5 pt-26"
|
||||
class="px-5 mpage-content mpage-content--with-subheader"
|
||||
:class="isDeposit ? 'pb-2' : 'pb-[calc(var(--app-bottom-chrome-height)+var(--app-add-card-height)+1rem)]'"
|
||||
>
|
||||
<a-form :model="form" class="!mt-5" name="basic" @finish="ok()">
|
||||
@@ -145,6 +145,7 @@
|
||||
<script lang="ts" setup>
|
||||
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import MobilePageHeader from '@/layouts/components/MobilePageHeader.vue'
|
||||
import BasicInput from '@/components/BasicPacks/BasicInput.vue'
|
||||
import Bg_main from '@/components/bg_main.vue'
|
||||
import ListAnim from '@/components/ListAnim.vue'
|
||||
|
||||
Reference in New Issue
Block a user