1404/11/08-store style

This commit is contained in:
2026-01-28 11:17:48 +03:30
parent 1d6f342ff1
commit 00c2e38ab9
23 changed files with 2047 additions and 127 deletions
-1
View File
@@ -12,7 +12,6 @@ declare module 'vue' {
ADivider: typeof import('ant-design-vue/es')['Divider']
AForm: typeof import('ant-design-vue/es')['Form']
AFormItem: typeof import('ant-design-vue/es')['FormItem']
AFormItemRest: typeof import('ant-design-vue/es')['FormItemRest']
AInput: typeof import('ant-design-vue/es')['Input']
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
AModal: typeof import('ant-design-vue/es')['Modal']
+11
View File
@@ -0,0 +1,11 @@
import useAxios from "@/utils/useAxios";
import type {PInventory, Product} from "@/models/products";
import type {PWallet, Wallet} from '@/models/wallet'
import type {Order} from "@/models/order";
import {useUserStore} from "@/store/userStore";
const userStore = useUserStore()
export const checkoutCart = async (orders: Order[]): Promise<any> => {
return await useAxios.post<Order[]>(`/customer/cart/checkout`, orders)
}
+1
View File
@@ -11,6 +11,7 @@ export const createOrder = async (obj: Order): Promise<any> => {
return await useAxios.post<Order>(`/customer${!userStore.isSingle ? '' : '/single'}/order`, obj)
}
export const getOrders = async (tbl: Ref<UnwrapRef<TableInput>>): Promise<TableOutput<Transaction>> => {
return await useAxios.post<TableOutput<Order>>(`/customer${!userStore.isSingle ? '' : '/single'}/order/table`, tbl)
}
+3
View File
@@ -6,6 +6,9 @@ import type { PWallet, Wallet } from '@/models/wallet'
export const getProducts = async (): Promise<any> => {
return await useAxios.get<Product[]>(`/customer/product/list`)
}
export const getProductById = async ( id:number|null): Promise<any> => {
return await useAxios.get<Product[]>(`/customer/product/list/${id}`)
}
export const getProductInventory = async (): Promise<any> => {
return await useAxios.post<PInventory[]>(`/customer/wallet/product/table`)
}
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 692 KiB

After

Width:  |  Height:  |  Size: 464 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 692 KiB

@@ -16,7 +16,7 @@
</style>-->
<template>
<VanActionSheet teleport="body">
<VanActionSheet :show="show" @update:show="$emit('update:show', $event)" :placement="placement" teleport="body">
<div class="justify-center cursor-pointer flex mt-1" @click="$emit('update:show', false)">
<span class="rounded-sm w-12 border-t-2 border-t-solid border-t-gray-500 mt-2"></span>
</div>
@@ -60,6 +60,8 @@ interface Props {
showCancel?: boolean
showOk?: boolean
okText?: string
show?: boolean
placement?: string
}
const props = withDefaults(defineProps<Props>(), {
@@ -68,6 +70,8 @@ const props = withDefaults(defineProps<Props>(), {
showCancel: true,
showOk: true,
okText: 'ثبت',
show: false,
placement: 'bottom'
})
</script>
+3 -3
View File
@@ -62,7 +62,7 @@ export function setEnableAuthenticate() {
function goToLogin(to: RouteLocationNormalized, next: NavigationGuardNext) {
if (to.name === 'buyProduct') {
if (to.name === 'buyProduct'||to.name === 'productDetail'||to.name==='about'||to.name==='cart') {
return next() // Allow access to buyProduct without login
}
if (to.name !== 'login') {
@@ -139,7 +139,7 @@ router.beforeEach(async (to, from, next) => {
}
} else {
// If no token, handle redirect to login or allow access to login, 404, or buyProduct
if (['login', '404', 'buyProduct'].includes(to.name as string)) {
if (['login', '404', 'buyProduct','productDetail','about','cart'].includes(to.name as string)) {
return next() // Allow access to login, 404, or buyProduct
}
goToLogin(to, next) // Redirect to login if no token
@@ -156,7 +156,7 @@ router.beforeEach(async (to, from, next) => {
return next() // Proceed to the intended route
} else {
// If no token, ensure proper redirection to login or 404
if (['login', '404', 'buyProduct'].includes(to.name as string)) {
if (['login', '404', 'buyProduct','productDetail','about','cart'].includes(to.name as string)) {
return next() // Allow access to login, 404, or buyProduct
}
return next({ name: 'login' })
+16
View File
@@ -0,0 +1,16 @@
import type { Product } from './products'
export interface CartItem {
id: string
product: Product
quantity: number
method: 'cash' | 'gold'
gateway: boolean
goldVazn?: number
goldCash: number
price: number
totalPrice: number
addedAt: number // timestamp
}
+1
View File
@@ -25,6 +25,7 @@ export interface Product {
sellPrice?:number | undefined
description: string
wallet: Wallet
similar?:Product[]
}
export interface PInventory {
+10
View File
@@ -35,6 +35,11 @@ const router = createRouter({
name: 'buyProduct',
component: () => import('@/views/base/products/index.vue')
},
{
path: 'product/:id',
name: 'productDetail',
component: () => import('@/views/base/products/product/[id].vue')
},
{
path: 'transactions',
name: 'transactions',
@@ -45,6 +50,11 @@ const router = createRouter({
name: 'wallet',
component: () => import('../views/base/wallet/index.vue')
},
{
path: 'cart',
name: 'cart',
component: () => import('@/views/base/cart/index.vue')
},
{
path: 'user',
name: 'user',
+109
View File
@@ -0,0 +1,109 @@
import { computed, ref } from 'vue'
import { defineStore } from 'pinia'
import type { CartItem } from '@/models/cart'
import type { Product } from '@/models/products'
const CART_STORAGE_KEY = 'ghatre_cart'
export const useCartStore = defineStore('cart', () => {
const cartItems = ref<CartItem[]>([])
// Load cart from localStorage on initialization
function loadCartFromStorage() {
try {
const stored = localStorage.getItem(CART_STORAGE_KEY)
if (stored) {
cartItems.value = JSON.parse(stored)
}
} catch (e) {
console.error('Error loading cart from storage:', e)
cartItems.value = []
}
}
// Save cart to localStorage
function saveCartToStorage() {
try {
localStorage.setItem(CART_STORAGE_KEY, JSON.stringify(cartItems.value))
} catch (e) {
console.error('Error saving cart to storage:', e)
}
}
// Initialize cart from storage
loadCartFromStorage()
// Add item to cart
function addToCart(
product: Product,
quantity: number,
method: 'cash' | 'gold',
gateway: boolean,
goldVazn?: number,
goldCash: number = 0
) {
const itemId = `${Date.now()}_${product.id}`
const price = (product.price ?? 0) + (product.cash_fee_buy ?? 0)
const totalPrice = price * quantity
const cartItem: CartItem = {
id: itemId,
product: { ...product },
quantity,
method,
gateway,
goldVazn,
goldCash,
price,
totalPrice,
addedAt: Date.now()
}
cartItems.value.push(cartItem)
saveCartToStorage()
}
// Remove item from cart
function removeFromCart(itemId: string) {
const index = cartItems.value.findIndex(item => item.id === itemId)
if (index !== -1) {
cartItems.value.splice(index, 1)
saveCartToStorage()
}
}
// Clear cart
function clearCart() {
cartItems.value = []
saveCartToStorage()
}
// Get cart items
function getCartItems(): CartItem[] {
return cartItems.value
}
// Computed: Total price
const totalPrice = computed(() => {
return cartItems.value.reduce((sum, item) => sum + item.totalPrice, 0)
})
// Computed: Item count
const itemCount = computed(() => {
return cartItems.value.length
})
return {
cartItems,
addToCart,
removeFromCart,
clearCart,
getCartItems,
totalPrice,
itemCount,
loadCartFromStorage,
saveCartToStorage
}
})
+6 -1
View File
@@ -108,6 +108,10 @@ function isIphone() {
return /iPhone/.test(userAgent)
}
function isToken() {
const token = getToken()
return !!token
}
export {
getToken,
@@ -118,5 +122,6 @@ export {
isIphone,
removeFingerPrint,
getIsActiveFingerprint,
setFingerPrint
setFingerPrint,
isToken
}
+3 -1
View File
@@ -31,6 +31,7 @@ export function inputLenRules(len: number, title?: string) {
import { computed, ref } from 'vue'
import type { Rule } from 'ant-design-vue/es/form'
import { useWalletStore } from '@/store/walletStore'
import {isToken} from "@/utils/index";
export const mobileValidation = (title?: string) => {
return { len: 11, message: `شماره همراه باید 11 رقم باشد` }
@@ -147,7 +148,7 @@ const checkNumber = async (_rule: Rule, value: number) => {
}
if (isBuyP) {
// Skip wallet balance check if gateway is enabled
if (!gatewayP) {
if (!gatewayP && isToken()) {
if (!walletCash?.walletInfo?.cash && methodP == 'cash') {
return Promise.reject('موجودی کیف پول ناکافی است')
}
@@ -162,6 +163,7 @@ const checkNumber = async (_rule: Rule, value: number) => {
return Promise.resolve()
} else {
if (value > NumP)
return Promise.reject('موجودی محصول ناکافی است')
else
return Promise.resolve()
+403
View File
@@ -0,0 +1,403 @@
<template>
<div>
<div class="absolute inset-0 bg-cover bg-center opacity-60 z--1" :style="{ backgroundImage: `url(${img})` }"></div>
<div class="bg-white drop-shadow z-[1999] wrap-fix">
<div class="py-3 px-3 font-bold flex items-center justify-between">
<div>سبد خرید</div>
<!-- Price Display in Header -->
<div v-if="price" class="flex gap-1 items-center text-xs flex-1 justify-end">
<span class="text-gray-600 text-xs">قیمت خرید از قطره:</span>
<div class="font-bold text-base" style="font-family: iran-sans; direction: ltr">
{{ usePriceWithoutType(price?.price_last ?? 0) }}
</div>
<span class="text-xs">ریال</span>
</div>
</div>
</div>
<GoldLoading v-model:show="infoVisible" title="" />
<div class="px-5 pt-26 pb-30">
<!-- Empty Cart Message -->
<div v-if="cartStore.itemCount === 0" class="flex flex-col items-center justify-center h-[60vh]">
<div class="text-gray-500 text-lg font-semibold mb-4">سبد خرید شما خالی است</div>
<BasicButton type="primary" @click="$router.push({ name: 'buyProduct' })">
مشاهده محصولات
</BasicButton>
</div>
<!-- Cart Items -->
<div v-else>
<div class="space-y-4 mb-20">
<div
v-for="item in cartStore.cartItems"
:key="item.id"
class="bg-white rounded-lg border border-gray-200 p-4 shadow-sm"
>
<div class="flex gap-4">
<!-- Product Image -->
<div class="bg-gray-100 rounded-lg flex justify-center items-center p-3 min-w-[80px] h-[80px]">
<img
:src="getProductImage(item.product.group_type)"
class="max-w-full max-h-full object-contain"
alt=""
/>
</div>
<!-- Product Info -->
<div class="flex-1 flex flex-col justify-between">
<div>
<div class="text-base font-semibold mb-1">{{ item.product.title }}</div>
<!-- <div class="text-xs text-gray-500 mb-2">-->
<!-- {{ getProductAmount(item.product.amount) }}-->
<!-- </div>-->
<div class="text-xs text-gray-500 mb-2">
روش پرداخت: {{ item.method === 'cash' ? 'نقدی' : 'طلایی' }}
<span v-if="item.method === 'cash' && item.gateway" class="mr-2">(با درگاه)</span>
</div>
<div class="text-xs text-gray-500">
تعداد: {{ item.quantity }} عدد
<span v-if="item.goldVazn" class="mr-2">| وزن: {{ formatWeight(item.goldVazn) }}</span>
</div>
</div>
<div class="flex items-center justify-between mt-2">
<div class="text-primary font-bold">
{{ usePrice(item.totalPrice) }}
</div>
<BasicIcon
icon="vuesax-linear:trash"
class="text-red-500 cursor-pointer"
@click.stop="handleRemoveItem(item.id)"
/>
</div>
</div>
</div>
</div>
</div>
<!-- Total and Checkout -->
<div class="fixed bottom-20 left-0 right-0 bg-white border-t border-gray-200 p-4 shadow-lg z-1">
<div class="flex justify-between items-center mb-4">
<div class="text-lg font-semibold">مجموع:</div>
<div class="text-primary font-bold text-xl">
{{ usePrice(totalPrice) }}
</div>
</div>
<BasicButton
class="w-full py-3"
type="primary"
size="large"
@click="handleCheckout"
>
تسویه حساب
</BasicButton>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch, computed } from 'vue'
import { useCartStore } from '@/store/cartStore'
import { usePrice, usePriceWithoutType } from '@/utils/usePrice'
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import { message, Modal } from 'ant-design-vue'
import img from '@/assets/img/user_ly1.svg'
import {
getLastPrices,
lastPrice,
stopPriceInterval,
setIntervalGetLastPrice
} from '@/utils/price'
import { checkoutCart } from '@/api/card'
import router from '@/router'
import { useWalletStore } from '@/store/walletStore'
import { useFundUserStore } from '@/store/fundStore'
import type { Price } from '@/models/home'
import type { Order } from '@/models/order'
// Image imports
import defaultImg from '@/assets/img/defaultAbshodeh.svg'
import goyImg from '@/assets/img/goyImg.svg'
import mokaabImg from '@/assets/img/mokaabImg.svg'
import sekkeImg from '@/assets/img/sekkeImg.svg'
import parsianImg from '@/assets/img/parsianImg.svg'
import venusImg from '@/assets/img/venousImg.svg'
import { loading, getToken } from '@/utils'
import GoldLoading from '@/components/GoldLoading.vue'
const cartStore = useCartStore()
const walletStore = useWalletStore()
const fundStore = useFundUserStore()
const infoVisible = ref(false)
const price = computed((): Price => <Price>lastPrice.value ?? 0)
// Watch for price updates and update cart items
watch(lastPrice, () => {
if (lastPrice.value && cartStore.cartItems.length > 0) {
cartStore.cartItems.forEach((item) => {
const product = item.product
let newPrice = 0
if (product.group_type == 'آبشده') {
newPrice = lastPrice.value?.price_last ?? 0
} else {
newPrice = product.amount * (lastPrice.value?.price_last ?? 0)
}
// Update product price
product.price = newPrice
product.sellPrice = product.amount * (lastPrice.value?.sell ?? 0)
// Update item price and total price
item.price = newPrice + (product.cash_fee_buy ?? 0)
item.totalPrice = item.price * item.quantity
})
// Save updated prices to storage
cartStore.saveCartToStorage()
}
}, { deep: true, immediate: false })
// Computed total price that updates with price changes
const totalPrice = computed(() => {
return cartStore.cartItems.reduce((sum, item) => sum + item.totalPrice, 0)
})
function getProductImage(type?: string) {
switch (type) {
case 'گوی':
return goyImg
case 'مکعب':
return mokaabImg
case 'سکه':
return sekkeImg
case 'شمش ونوس':
return venusImg
case 'سکه پارسیان':
return parsianImg
default:
return defaultImg
}
}
function getProductAmount(amount?: number) {
if (!amount) return ''
if (amount < 1) {
// کمتر از 1 گرم را به سوت تبدیل کن (مشابه ProductItem)
const sot = amount * 1000
// اگر نتیجه کمتر از 1 شد، حداقل 1 نمایش بده
if (sot < 1) {
return `1 سوت`
}
// در غیر این صورت، عدد را به عدد صحیح گرد کن
return `${Math.round(sot)} سوت`
}
return `${amount} گرم`
}
function formatWeight(weight?: number) {
if (!weight) return ''
if (weight < 1) {
// کمتر از 1 گرم را به سوت تبدیل کن
const sot = weight * 1000
// اگر نتیجه کمتر از 1 شد، حداقل 1 نمایش بده
if (sot < 1) {
return `1 سوت`
}
// در غیر این صورت، عدد را به عدد صحیح گرد کن
return `${Math.round(sot)} سوت`
}
return `${weight} گرم`
}
function handleRemoveItem(itemId: string) {
Modal.confirm({
title: 'حذف از سبد خرید',
content: 'آیا از حذف این آیتم از سبد خرید اطمینان دارید؟',
okText: 'تایید',
okType: 'danger',
centered: true,
cancelText: 'انصراف',
onOk() {
cartStore.removeFromCart(itemId)
message.success('آیتم از سبد خرید حذف شد')
}
})
}
async function handleCheckout() {
// Check if user is logged in
if (!getToken()) {
message.warning('برای تسویه حساب، لطفا ابتدا وارد حساب کاربری خود شوید')
await router.push({ name: 'login' })
return
}
if (cartStore.itemCount === 0) {
message.warning('سبد خرید شما خالی است')
return
}
if (!lastPrice.value) {
message.error('لطفا منتظر بمانید تا قیمت‌ها بارگذاری شوند')
return
}
// Check wallet balance for cash method items (without gateway)
const cashItemsTotal = cartStore.cartItems
.filter(item => item.method === 'cash' && !item.gateway)
.reduce((sum, item) => sum + item.totalPrice, 0)
if (cashItemsTotal > 0) {
// Ensure wallet info is loaded
if (!walletStore.walletInfo) {
await walletStore.getMyWallet()
}
if (!walletStore.walletInfo?.cash || cashItemsTotal > walletStore.walletInfo.cash) {
message.error('موجودی کیف پول شما ناکافی است')
return
}
}
// Check gold balance for gold method items
const goldItemsTotal = cartStore.cartItems
.filter(item => item.method === 'gold')
.reduce((sum, item) => {
// Calculate total gold weight needed
const goldWeight = item.goldVazn ? item.goldVazn * item.quantity : 0
return sum + goldWeight
}, 0)
if (goldItemsTotal > 0) {
// Ensure wallet info is loaded
if (!walletStore.walletInfo) {
await walletStore.getMyWallet()
}
if (!walletStore.walletInfo?.gold || goldItemsTotal > walletStore.walletInfo.gold) {
message.error('موجودی کیف طلا شما ناکافی است')
return
}
}
try {
loading.value = true
infoVisible.value = true
// Prepare orders array with same structure as createOrder for each product
const orders: Order[] = cartStore.cartItems.map(item => {
const product = item.product
// Calculate price similar to createOrder (buy price)
let productPrice = 0
if (product.group_type == 'آبشده') {
productPrice = lastPrice.value?.price_last ?? 0
} else {
productPrice = (product.amount ?? 0) * (lastPrice.value?.price_last ?? 0)
}
return {
direction: 1, // 1 for buy (cart items are always buy orders)
price: productPrice,
amount: item.quantity,
gateway: item.gateway,
product_id: product.id,
method: item.method,
number: item.quantity
}
})
// Call checkoutCart API with orders array
const data = await checkoutCart(orders)
// Check for returnUrl (gateway payment)
let returnUrl = null
if (Array.isArray(data)) {
const itemWithReturnUrl = data.find(item => item?.returnUrl)
if (itemWithReturnUrl?.returnUrl) {
returnUrl = itemWithReturnUrl.returnUrl
}
} else if (data?.returnUrl) {
returnUrl = data.returnUrl
}
// If returnUrl exists, redirect to gateway and stop execution
if (returnUrl) {
window.location.href = returnUrl
return // Stop execution here
}
// If no gateway, continue with normal flow
await walletStore.getMyWallet()
cartStore.clearCart()
await router.push({ name: 'transactions' })
message.success('سفارشات شما با موفقیت ثبت شد')
await fundStore.getMyFund()
} catch (e: any) {
console.error('Error in checkout:', e)
message.error('خطا در ثبت سفارش. لطفا دوباره تلاش کنید.')
} finally {
loading.value = false
infoVisible.value = false
}
}
async function initPrice() {
try {
await getLastPrices()
await setIntervalGetLastPrice()
// Update prices for all cart items
if (lastPrice.value && cartStore.cartItems.length > 0) {
cartStore.cartItems.forEach((item) => {
const product = item.product
let newPrice = 0
if (product.group_type == 'آبشده') {
newPrice = lastPrice.value?.price_last ?? 0
} else {
newPrice = product.amount * (lastPrice.value?.price_last ?? 0)
}
// Update product price
product.price = newPrice
product.sellPrice = product.amount * (lastPrice.value?.sell ?? 0)
// Update item price and total price
item.price = newPrice + (product.cash_fee_buy ?? 0)
item.totalPrice = item.price * item.quantity
})
// Save updated prices to storage
cartStore.saveCartToStorage()
}
} catch (e: any) {
console.log(e)
}
}
onMounted(async () => {
// Load cart from storage if needed
cartStore.loadCartFromStorage()
await initPrice()
})
onUnmounted(() => {
stopPriceInterval()
})
</script>
<style scoped>
.text-primary {
color: #d3a233;
}
@media screen and (min-width: 426px) {
.wrap-fix {
width: 426px;
margin: 0 auto;
}
}
</style>
+61 -13
View File
@@ -22,49 +22,49 @@
<div>حساب کاربری</div>
</div>
</BasicButton>
<BasicButton icon-size="16" class="!font-bold w-15rem"
<BasicButton v-if="isToken()" icon-size="16" class="!font-bold w-15rem"
@click="$router.push({name: 'notifyTrigger'}); popVisible = false">
<div class="flex gap-2 items-center">
<BasicIcon icon="vuesax-linear:bill" size="16" class="absolute right-2" />
<div>هشدار قیمت</div>
</div>
</BasicButton>
<BasicButton icon-size="16" class="!font-bold w-15rem"
<BasicButton v-if="isToken()" icon-size="16" class="!font-bold w-15rem"
@click="$router.push({name: 'transfer'}); popVisible = false">
<div class="flex gap-2 items-center">
<BasicIcon icon="vuesax-linear:card-send" size="16" class="absolute right-2" />
<div>انتقال قطره</div>
</div>
</BasicButton>
<BasicButton icon-size="16" class="!font-bold w-15rem"
<BasicButton v-if="isToken()" icon-size="16" class="!font-bold w-15rem"
@click="$router.push({name: 'physicalReceive'}); popVisible = false">
<div class="flex gap-2 items-center">
<BasicIcon icon="vuesax-linear:direct-inbox" size="16" class="absolute right-2" />
<div>دریافت طلای فیزیکی</div>
</div>
</BasicButton>
<BasicButton icon-size="16" class="!font-bold w-15rem"
<BasicButton v-if="isToken()" icon-size="16" class="!font-bold w-15rem"
@click="$router.push({name: 'buyProduct'}); popVisible = false">
<div class="flex gap-2 items-center">
<BasicIcon icon="vuesax-linear:coin" size="16" class="absolute right-2" />
<div>خرید محصول</div>
</div>
</BasicButton>
<BasicButton icon-size="16" class="!font-bold w-15rem"
<BasicButton v-if="isToken()" icon-size="16" class="!font-bold w-15rem"
@click="openReceivesList(); popVisible = false">
<div class="flex gap-2 items-center">
<BasicIcon icon="vuesax-linear:receipt-item" size="16" class="absolute right-2" />
<div>لیست دریافتی ها</div>
</div>
</BasicButton>
<BasicButton icon-size="16" class="!font-bold w-15rem"
<BasicButton icon-size="16" class="!font-bold w-15rem"
@click="$router.push({name: 'about'}); popVisible = false">
<div class="flex gap-2 items-center">
<BasicIcon icon="vuesax-linear:info-circle" size="16" class="absolute right-2" />
<div>درباره ما</div>
</div>
</BasicButton>
<BasicButton icon-size="16" class="!font-bold w-15rem"
<BasicButton v-if="isToken()" icon-size="16" class="!font-bold w-15rem"
@click="$router.push({name: 'rules'}); popVisible = false">
<div class="flex gap-2 items-center">
<BasicIcon icon="vuesax-linear:task" size="16" class="absolute right-2" />
@@ -98,7 +98,7 @@
</router-view>
</div>
<!-- <canvas ref="canvas" id="canvas1" width="400" height="80" class="w-full"></canvas>-->
<div class="bottom-appbar w-auto mx-auto">
<div v-if="isToken()" class="bottom-appbar w-auto mx-auto">
<div class="tabs">
<div :class="setNavColor('home')" class="tab" @click="setRoute('home')">
<Icon icon="vuesax-linear:home" />
@@ -128,9 +128,13 @@
<Icon icon="vuesax-linear:wallet-1" />
<div class="text-xs mt-1 font-bold">کیف پول</div>
</div>
<div :class="setNavColor('physicalReceive')" class="tab" @click="setRoute('physicalReceive')">
<Icon icon="vuesax-linear:direct-inbox" />
<div class="text-xs mt-1 font-bold">دریافت فیزیکی</div>
<!-- <div :class="setNavColor('physicalReceive')" class="tab" @click="setRoute('physicalReceive')">-->
<!-- <Icon icon="vuesax-linear:direct-inbox" />-->
<!-- <div class="text-xs mt-1 font-bold">دریافت فیزیکی</div>-->
<!-- </div>-->
<div :class="setNavColor('cart')" class="tab" @click="setRoute('cart')">
<Icon icon="vuesax-linear:shopping-cart" />
<div class="text-xs mt-1 font-bold">سبد خرید</div>
</div>
</div>
<div class="main">
@@ -138,16 +142,58 @@
</div>
</div>
</div>
<div v-else class="bottom-appbar w-auto mx-auto">
<div class="tabs">
<div :class="setNavColor('buyProduct')" class="tab" @click="setRoute('buyProduct')">
<Icon icon="vuesax-linear:coin" />
<div class="text-xs mt-1 font-bold">فروشگاه</div>
</div>
<div :class="setNavColor('about')" class="tab" @click="setRoute('about')">
<Icon icon="vuesax-linear:info-circle" />
<div class="text-xs mt-1 font-bold">درباره ما</div>
</div>
<div class="main_in">
<div class="text-white circle_in">
<div class="circle_into " @click="popVisible = !popVisible">
<img
class="p-2"
src="@/assets/img/logo.png"
/>
</div>
<!-- <div class="flex justify-center">خرید </div>
<div class="flex justify-center">محصول </div>-->
</div>
</div>
<!-- <div :class="setNavColor('buyProduct')" class="tab" @click="setRoute('buyProduct')">
<Icon icon="vuesax-linear:coin" />
<div class="text-xs mt-1 font-bold">خرید محصول</div>
</div>-->
<div :class="setNavColor('cart')" class="tab" @click="setRoute('cart')">
<Icon icon="vuesax-linear:shopping-cart" />
<div class="text-xs mt-1 font-bold">سبد خرید</div>
</div>
<div :class="setNavColor('login')" class="tab" @click="setRoute('login')">
<Icon icon="vuesax-linear:login" />
<div class="text-xs mt-1 font-bold">ورود</div>
</div>
</div>
<div class="main">
<div class="circle">
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
import {computed, onMounted, ref} from 'vue'
import Icon from '@/components/BasicPacks/BasicIcon.vue'
import router from '@/router'
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
import type { Transaction } from '@/models/transaction'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import {isToken} from "@/utils";
const currentRoute = computed(() => router.currentRoute.value.name)
@@ -169,7 +215,9 @@ function setRoute(val: string) {
function openReceivesList() {
router.push({ name: 'list' })
}
onMounted(()=>{
console.log(isToken())
})
</script>
@@ -9,15 +9,15 @@
:style="!props?.ProductItem?.open_buy ? 'pointer-events: none; opacity: 0.5;' : ''">
خرید {{ props?.ProductItem?.title }}
</div>
<div :class="!isBuy?'text-primary':'text-gray-400'" class="w-full text-center z-2"
@click.stop="setIsBuy(false) "
:style="!props?.ProductItem?.open_sell ? 'pointer-events: none; opacity: 0.5;' : ''"
>
فروش {{ props?.ProductItem?.title }}
</div>
<!-- <div :class="!isBuy?'text-primary':'text-gray-400'" class="w-full text-center z-2"-->
<!-- @click.stop="setIsBuy(false) "-->
<!-- :style="!props?.ProductItem?.open_sell ? 'pointer-events: none; opacity: 0.5;' : ''"-->
<!-- >-->
<!-- فروش {{ props?.ProductItem?.title }}-->
<!-- </div>-->
<!-- <div ref="bganim" class="absolute bg-white h-8 w-1/2 rounded-lg " :style="`transform: translateX(${isbuy? '0':'-100%'}); left: ${isbuy? '0':'100%'}`"></div>-->
<div ref="bganim" :style="useStyle()"
class="absolute bg-white h-8 w-1/2 rounded-lg animate right"></div>
class="absolute bg-white h-8 w-full rounded-lg animate right"></div>
</div>
</div>
<div v-if="userStore" class=" mt-5 pb-2 mb-19vh">
@@ -104,16 +104,16 @@
</Transition>
</div>
</div>
<a-form-item-rest v-if="isBuy && form.method === 'cash'">
<div class="mt-4 w-full px-6 rounded-xl py-1 border-1 bg-white">
<div class="flex justify-between items-center">
<div>استفاده از درگاه:</div>
<div class="flex gap-2 items-center">
<a-switch v-model:checked="form.gateway" size="small" />
</div>
</div>
</div>
</a-form-item-rest>
<!-- <a-form-item-rest v-if="isBuy && form.method === 'cash'">-->
<!-- <div class="mt-4 w-full px-6 rounded-xl py-1 border-1 bg-white">-->
<!-- <div class="flex justify-between items-center">-->
<!-- <div>استفاده از درگاه:</div>-->
<!-- <div class="flex gap-2 items-center">-->
<!-- <a-switch v-model:checked="form.gateway" size="small" />-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </a-form-item-rest>-->
<GoldLoading v-model:show="infoVisible" title="" />
<a-form-item>
<BasicButton
@@ -126,7 +126,7 @@
>
<div>
<a-spin v-if="loading" size="small" />
<div v-else>{{ !isBuy ? 'فروش' : 'خرید' }}</div>
<div v-else>{{ !isBuy ? 'فروش' : 'افزودن به سبد خرید' }}</div>
</div>
</BasicButton>
<!-- <div class=" w-full !z-100 px-6 rounded-xl py-1 border-1 bg-white mt-2">
@@ -267,6 +267,7 @@ import BasicSelect from '@/components/BasicPacks/BasicSelect.vue'
import { SelectItem } from '@/models/SelectItem'
import Bg_main from '@/components/bg_main.vue'
import { useFundUserStore } from '@/store/fundStore'
import { useCartStore } from '@/store/cartStore'
const typeDeal: SelectItem[] = [
{
@@ -282,8 +283,13 @@ const typeDeal: SelectItem[] = [
const infoVisible = ref(false)
const walletStore = useWalletStore()
const userStore = useUserStore()
const cartStore = useCartStore()
const confirmAction = ref<boolean>(false)
const noCompleteVisible = ref<boolean>(false)
const emit = defineEmits<{
close: []
}>()
const buyItems = ref<Transaction[]>([])
const sellItems = ref<Transaction[]>([])
const priceErrorVisible = ref<boolean>(false)
@@ -667,10 +673,34 @@ async function okConfirm() {
console.log('okConfirm called, form values:', form.value)
console.log('disableTransaction:', disableTransaction.value)
await refInput.value?.validateFields()
console.log('Validation passed, showing confirmation action sheet')
// If validation passes, show confirmation action sheet
confirmAction.value = true
console.log('confirmAction set to:', confirmAction.value)
// If buying, add to cart instead of showing confirmation
if (isBuy.value) {
cartStore.addToCart(
props.ProductItem!,
form.value.goldValue ?? 0,
form.value.method as 'cash' | 'gold',
form.value.gateway ?? false,
form.value.goldVazn,
form.value.goldCash ?? 0
)
message.success('محصول به سبد خرید اضافه شد')
emit('close')
// Reset form
form.value = {
goldValue: undefined,
goldCash: undefined,
percentWallet: undefined,
method: 'cash',
goldVazn: undefined,
gateway: false
}
} else {
// For selling, show confirmation action sheet (old behavior)
console.log('Validation passed, showing confirmation action sheet')
confirmAction.value = true
console.log('confirmAction set to:', confirmAction.value)
}
} catch (error) {
// Validation failed, don't show action sheet
console.error('Validation failed:', error)
@@ -0,0 +1,787 @@
<template>
<div class="h-35rem overflow-y-auto">
<div class=" !overflow-y-scroll bg-white py-3 text-primary font-bold flex justify-evenly">
<!-- <div @click="isBuy = false" class="w-full text-center" :class="!isBuy?'text-primary':'text-gray-400'">خرید طلا</div>
<div @click="isBuy = true" class="w-full text-center" :class="isBuy?'text-primary':'text-gray-400'">فروش طلا</div>-->
<div class="flex justify-evenly w-full px-2 h-11 items-center bg-gray-50 relative rounded ">
<div :class="isBuy?'text-primary':'text-gray-400'" class="w-full text-center z-2"
@click.stop="setIsBuy(true) "
:style="!props?.ProductItem?.open_buy ? 'pointer-events: none; opacity: 0.5;' : ''">
خرید {{ props?.ProductItem?.title }}
</div>
<div :class="!isBuy?'text-primary':'text-gray-400'" class="w-full text-center z-2"
@click.stop="setIsBuy(false) "
:style="!props?.ProductItem?.open_sell ? 'pointer-events: none; opacity: 0.5;' : ''"
>
فروش {{ props?.ProductItem?.title }}
</div>
<!-- <div ref="bganim" class="absolute bg-white h-8 w-1/2 rounded-lg " :style="`transform: translateX(${isbuy? '0':'-100%'}); left: ${isbuy? '0':'100%'}`"></div>-->
<div ref="bganim" :style="useStyle()"
class="absolute bg-white h-8 w-1/2 rounded-lg animate right"></div>
</div>
</div>
<div v-if="userStore" class=" mt-5 pb-2 mb-19vh">
<!-- <a-spin :spinning="loading">-->
<div v-if="!loading" :class="!isBuy? ' bg-red-100' : ' bg-green-100'"
class="w-full relative rounded-lg h-[88px] flex flex-col justify-center background-color-transition">
<img :src="img1" alt="" class="h-[88px] absolute">
<div class="w-full items-center flex flex-col">
<div class="text-gray-700 font-bold">{{ !isBuy ? 'قیمت فروش' : 'قیمت خرید' }}</div>
<div v-if="price" class="flex gap-1 mt-3 text-xs items-center">
<div :class="!isBuy? 'text-red-700' : 'text-green-700'" class="font-bold text-2xl color-transition">
{{ usePriceWithoutType(isBuy ? ((props?.ProductItem?.price ?? 0) + (props?.ProductItem?.cash_fee_buy ?? 0)) : ((props?.ProductItem?.price ?? 0) - (props?.ProductItem?.cash_fee_sell ?? 0))) }}
</div>
ریال
</div>
</div>
</div>
<div v-else class="w-full h-[88px]">
<GoldSkeleton class="w-full" />
</div>
<div v-if="limitedPrice !== undefined && userStore?.userInfo.trade_active !== 1"
class="text-red text-xs flex gap-1 items-center justify-center my-2">
<Icon icon="vuesax-linear:info-circle" size="16" />
حداقل مبلغ اولیه برای ثبت سفارش {{ usePriceWithoutType(limitedPrice.value as number ?? 0) }} ریال می باشد.
</div>
<!-- </a-spin>-->
<a-form ref="refInput" :model="form" class="!mt-5" name="basic" @finish="okConfirm()" @finishFailed="(errorInfo) => { console.log('Form submit failed:', errorInfo); console.log('Error fields details:', JSON.stringify(errorInfo.errorFields, null, 2)); if (errorInfo.errorFields && errorInfo.errorFields.length > 0) { const firstError = errorInfo.errorFields[0]; console.log('First error field:', firstError.name, 'errors:', firstError.errors); message.error(firstError.errors?.[0] || 'لطفا تمام فیلدها را به درستی پر کنید') } }">
<div class="border-gray-200 border-1 rounded-xl p-5 relative">
<bg_main />
<div class="z-100 relative">
<a-form-item name="method" :rules="[required()]">
<BasicSelect v-model:value="form.method" :items="typeDeal"
key-name="key"
:show-search="false"
value-name="value"
:allow-clear="false"
:placeholder="isBuy?'نحوه خرید را انتخاب کنید':' نحوه فروش را انتخاب کنید'"
size="large"
:has-tooltip="false" />
</a-form-item>
<a-form-item v-if="isBuy" name="goldValue"
:rules="[justNumber2(props?.ProductItem?.price , form.method , props?.ProductItem?.amount, true, form.gateway)]">
<BasicInput v-model:value="form.goldValue" addon-before="تعداد" addon-bg-color="#521620"
addon-text-color="white"
place-holder="تعداد"
size="large" type="number" inputmode="number" @onchange="calculateCash" />
</a-form-item>
<a-form-item v-else name="goldValue"
:rules="[justNumber3(props?.ProductItem?.sellPrice, productNum ,false)]">
<BasicInput v-model:value="form.goldValue" addon-before="تعداد" addon-bg-color="#521620"
addon-text-color="white"
place-holder="تعداد"
size="large" type="number" inputmode="number" @onchange="calculateCash" />
</a-form-item>
<div class="flex justify-between">
<div> قیمت کل</div>
<div>{{ usePrice(form.goldCash ?? 0) }}</div>
</div>
<div v-if="props?.ProductItem?.group_type !== 'سکه'" class="flex justify-between">
<div> وزن کل</div>
<div>{{ form.goldVazn ?? 0 }} گرم</div>
</div>
<!-- {{walletStore?.walletInfo?.gold}}-->
<Transition mode="out-in" name="fade" class="mt-2">
<div v-if="isBuy&&form.method=='gold'" class="font-bold text-xs">
<div> {{ `موجودی کیف طلا: ${walletStore?.walletInfo?.gold ?? 0} گرم` }}</div>
</div>
<div v-else-if="isBuy" class="font-bold text-xs">
{{ `موجودی کیف پول: ${usePriceWithoutType(walletStore?.walletInfo?.cash ?? 0)} ریال` }}
</div>
<div class="font-bold text-xs" v-else>{{ `موجودی محصول: ${getInventoryById(props?.ProductItem?.id)}` }}
</div>
</Transition>
<Transition mode="out-in" name="fade">
<div v-if="priceErrorVisible" class="flex gap-1 items-center text-xs mt-[-15px] text-orange-600">
<BasicIcon icon="vuesax-linear:danger" />
ارزش کل وارد شده بر حسب گرم طلا محاسبه خواهد شد و قیمت وارد شده معادل {{ usePrice(finalPrice) }} می
باشد!
</div>
</Transition>
</div>
</div>
<a-form-item-rest v-if="isBuy && form.method === 'cash'">
<div class="mt-4 w-full px-6 rounded-xl py-1 border-1 bg-white">
<div class="flex justify-between items-center">
<div>استفاده از درگاه:</div>
<div class="flex gap-2 items-center">
<a-switch v-model:checked="form.gateway" size="small" />
</div>
</div>
</div>
</a-form-item-rest>
<GoldLoading v-model:show="infoVisible" title="" />
<a-form-item>
<BasicButton
v-if="walletStore"
:disabled="disableTransaction"
class=" w-full py-2 mt-5"
html-type="submit"
type="primary"
@click="() => console.log('Button clicked, disabled:', disableTransaction, 'walletStore:', !!walletStore)"
>
<div>
<a-spin v-if="loading" size="small" />
<div v-else>{{ !isBuy ? 'فروش' : 'خرید' }}</div>
</div>
</BasicButton>
<!-- <div class=" w-full !z-100 px-6 rounded-xl py-1 border-1 bg-white mt-2">
&lt;!&ndash; <BasicInfoItem is-line title="کارمزد" :value="parseFloat(((form.goldValue?? 0)*0.005).toFixed(3))" />&ndash;&gt;
&lt;!&ndash; <BasicInfoItem is-line title="کارمزد" :value="parseFloat(((form.goldValue?? 0)*0.005).toFixed(3))" />&ndash;&gt;
<div class="flex justify-between items-center">
<div>کارمزد:</div>
<div class="flex gap-2 items-center">
&lt;!&ndash; <div class="line-through">{{ parseFloat(((form.goldValue ?? 0) * 0.005).toFixed(3)) }}</div>&ndash;&gt;
&lt;!&ndash; <div>{{props.ProductItem.price}}</div>&ndash;&gt;
<div v-if="isBuy && form.method=='cash' " class="">{{
usePriceWithoutType(buyFee ?? 0)
}}
</div>
<div v-if="isBuy && form.method!='cash'" class="">{{
buyFeeGeram ?? 0
}}
</div>
<div v-if="!isBuy && form.method=='cash' " class="">{{
usePriceWithoutType(sellFee ?? 0)
}}
</div>
<div v-if="!isBuy && form.method!='cash'" class="">{{
sellFeeGeram ?? 0
}}
</div>
<div>
{{ form.method == "cash" ? "ریال" : "گرم" }}
</div>
</div>
</div>
</div>-->
<!-- <div v-if="!isBuy && form.goldValue > walletStore?.walletInfo?.gold"
class="text-red-700 text-xs font-bold mt-2">
مقدار گرم طلای وارد شده جهت فروش ({{ form.goldValue }}گرم) بیشتر از مقدار موجودی شما
({{ walletStore?.walletInfo?.gold }}گرم) می باشد.
</div>
&lt;!&ndash; <div>{{form?.goldValue? true: false}}</div>&ndash;&gt;
<div v-if="isBuy? ((form?.goldValue || form.goldValue === 0)? form?.goldCash < 500000: false) : false"
class="text-red-700 text-xs font-bold mt-2">
حداقل مقدار طلا جهت خرید 500,000 ریال می باشد.
</div>-->
</a-form-item>
</a-form>
</div>
<van-dialog v-model:show="noCompleteVisible" class="completeButtonStyle" confirm-button-text="تکمیل اطلاعات"
show-confirm-button @confirm="router.push('user')">
<div class="flex flex-col items-center p-2">
<BasicIcon class="home_appear_fade_in_up text-center" color="orange"
icon="vuesax-linear:info-circle"
size="50" />
<!-- <div class="home_appear_fade_in_up delay-100">به روز رسانی</div>-->
<div class="home_appear_fade_in_up mt-2 text-justify text-center">برای ثبت سفارش، لطفا اطلاعات خود را تکمیل کنید
یا در صورت تکمیل اطلاعات، منتظر تایید اطلاعات خود بمانید.
</div>
</div>
</van-dialog>
</div>
<BasicActionSheet v-model:show="confirmAction">
<div class="font-bold text-lg"></div>
<div class="w-full rounded-lg bg-gray-50 p-3">
<div class="flex justify-between ">
<div>تعداد:</div>
<div class="flex items-center">
<div class="font-bold">{{ form.goldValue }}</div>
<div class="text-xs mr-1"> عدد</div>
<div class="text-base font-bold mr-1"> عدد {{ props?.ProductItem?.title }}</div>
</div>
</div>
<div class="flex justify-between mt-3">
<div>قیمت:</div>
<div class="flex items-center">
<div class="font-bold">
{{ usePriceWithoutType(isBuy ? props?.ProductItem.price ?? 0 : props?.ProductItem?.sellPrice ?? 0) }}
</div>
<div class="text-xs mr-1">ریال</div>
</div>
</div>
<div class="flex justify-between mt-3">
<div>کارمزد:</div>
<div class="flex items-center gap-2">
<div v-if="isBuy" class="">{{
buyFee
}}
</div>
<!-- <div class="font-bold line-through">{{ 0 }}</div>-->
<div v-else class="">{{
sellFee
}}
</div>
<div class="text-xs mr-1">ریال</div>
</div>
</div>
<div class="flex justify-between mt-3">
<div>{{ isBuy ? 'قیمت قابل پرداخت:' : 'مبلغ دریافتی:' }}</div>
<div class="flex items-center">
<div class="font-bold">{{ usePriceWithoutType(payablePrice.toFixed(0)) }}</div>
<div class="text-xs mr-1">ریال</div>
</div>
</div>
</div>
<BasicButton class="mb-20 mt-5 w-full" type="primary" @click="() => { ok(); confirmAction = false }">ثبت نهایی</BasicButton>
</BasicActionSheet>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref, watch } from 'vue'
import img1 from '@/assets/img/home_ly2.svg'
import {useFormat, usePrice, usePriceWithoutType} from '@/utils/usePrice'
import BasicInput from '@/components/BasicPacks/BasicInput.vue'
import type { Gold } from '@/models/gold'
import { loading, loadingMore } from '@/utils'
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
import router from '@/router'
import { useWalletStore } from '@/store/walletStore'
import { createOrder } from '@/api/order'
import { FormInstance, message } from 'ant-design-vue'
import type { Price } from '@/models/home'
import Icon from '@/components/BasicPacks/BasicIcon.vue'
import { useUserStore } from '@/store/userStore'
import BasicActionSheet from '@/components/BasicPacks/BasicActionSheet.vue'
import { justNumber, justNumber2, justNumber3, required, ruleNumber } from '@/utils/inputRules'
import GoldSkeleton from '@/views/base/gold/components/GoldSkeleton.vue'
import GoldLoading from '@/components/GoldLoading.vue'
// import {update} from 'lodash'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import type { Transaction } from '@/models/transaction'
import type { TableEngineInput, TableOutput } from '@/models/table'
import { getTransactionTable } from '@/api/transaction'
import { getLastPrices, lastPrice } from '@/utils/price'
import { Product } from '@/models/products'
import BasicSelect from '@/components/BasicPacks/BasicSelect.vue'
import { SelectItem } from '@/models/SelectItem'
import Bg_main from '@/components/bg_main.vue'
import { useFundUserStore } from '@/store/fundStore'
const typeDeal: SelectItem[] = [
{
value: 'نقدی',
key: 'cash'
},
{
value: ' طلایی',
key: 'gold'
}
]
const infoVisible = ref(false)
const walletStore = useWalletStore()
const userStore = useUserStore()
const confirmAction = ref<boolean>(false)
const noCompleteVisible = ref<boolean>(false)
const buyItems = ref<Transaction[]>([])
const sellItems = ref<Transaction[]>([])
const priceErrorVisible = ref<boolean>(false)
const buyTableOutput = ref<TableOutput<Transaction> | null>(null)
const sellTableOutput = ref<TableOutput<Transaction> | null>(null)
const fundStore = useFundUserStore()
const refInput = ref<FormInstance>()
const el = ref()
interface Prop {
ProductItem?: Product
}
const props = withDefaults(defineProps<Prop>(), {})
const isBuy = ref<boolean>(true)
const buyFee = computed(() => ((props?.ProductItem?.amount * (props?.ProductItem?.percent_fee_buy ?? 0) * (props?.ProductItem?.price ?? 0)).toFixed(0)))
const sellFee = computed(() => ((props?.ProductItem?.amount * (props?.ProductItem?.percent_fee_sell ?? 0) * (props?.ProductItem?.sellPrice ?? 0)).toFixed(0)))
const sellFeeGeram = computed(() => {
if (form.value.goldVazn)
return (form.value.goldVazn * props.ProductItem?.percent_fee_sell).toFixed(3)
})
const buyFeeGeram = computed(() => {
if (form.value.goldVazn)
return (form.value.goldVazn * props.ProductItem?.percent_fee_buy).toFixed(3)
})
const buyPagination = ref<TableEngineInput>({
filters: [{
field: 'type',
cr: 'and',
type: 'number',
op: 'eq',
value: 1
}], limit: 6, page: 0
})
const sellPagination = ref<TableEngineInput>({
filters: [{
field: 'type',
cr: 'and',
type: 'number',
op: 'eq',
value: 2
}], limit: 6, page: 0
})
const finalPrice = computed(() => (form.value?.goldValue * (isBuy.value ? props.ProductItem?.price ?? 0 : props.ProductItem?.sellPrice ?? 0)))
// const payablePrice = computed(() => finalPrice.value + finalPrice.value * 0.007)
const payablePrice = computed(() => finalPrice.value)
const disableTransaction = computed(() => {
// If gateway is enabled, skip wallet balance checks
if (isBuy.value && form.value?.gateway) {
return (
loading.value ||
form.value?.goldValue === undefined ||
form.value?.goldValue === '' ||
form.value?.goldCash === '' ||
form.value?.goldCash === undefined ||
price.value?.buy === undefined
)
}
// Normal validation when gateway is disabled
return (
loading.value ||
form.value?.goldValue === undefined ||
form.value?.goldValue === '' ||
form.value?.goldCash === '' ||
form.value?.goldCash === undefined ||
(isBuy.value ? (form.value?.goldCash ?? 0) < 500000 : (form.value?.goldValue ?? 0) < 0.001) ||
// (!isBuy.value && form.value.goldValue > walletStore?.walletInfo?.gold) ||
price.value?.buy === undefined
)
})
function refreshTable() {
sellItems.value = []
buyItems.value = []
sellPagination.value = {
filters: [{
field: 'type',
cr: 'and',
type: 'number',
op: 'eq',
value: 2
}], limit: 6, page: 0
}
buyPagination.value = {
filters: [{
field: 'type',
cr: 'and',
type: 'number',
op: 'eq',
value: 1
}], limit: 6, page: 0
}
/* initSellTable()
initBuyTable()*/
}
async function initBuyTable() {
try {
buyPagination.value.page === 0 ? loading.value = true : loadingMore.value = true
buyTableOutput.value = await getTransactionTable(buyPagination.value)
buyItems.value.push(...buyTableOutput.value.rows)
// console.log(tableOutput.value)
} catch (e) {
console.log(e)
} finally {
loading.value = false
loadingMore.value = false
}
}
async function initSellTable() {
try {
sellPagination.value.page === 0 ? loading.value = true : loadingMore.value = true
sellTableOutput.value = await getTransactionTable(sellPagination.value)
sellItems.value.push(...sellTableOutput.value.rows)
// console.log(tableOutput.value)
} catch (e) {
console.log(e)
} finally {
loading.value = false
loadingMore.value = false
}
}
// if (!userStore.isSingle) {
/*useInfiniteScroll(
el,
async () => {
// load more
// console.log('salamss')
if (!userStore.isSingle) {
if (isbuy.value) {
// console.log((buyeTableOutput.value.query.page))
// console.log(buyeTableOutput.value.query.total)
// console.log((buyeTableOutput.value.query.page + 1) * buyPagination.value.limit < buyeTableOutput.value.query.total)
if (
buyeTableOutput.value &&
(buyPagination.value.page + 1) * buyPagination.value.limit < buyeTableOutput.value.query.total
) {
// console.log('gfgf')
buyPagination.value.page++
await initbuyTable()
}
} else {
if (
sellTableOutput.value &&
(sellPagination.value.page + 1) * sellPagination.value.limit < sellTableOutput.value.query.total
) {
// console.log('gfgf')
sellPagination.value.page++
await initsellTable()
}
}
}
},
{distance: 12}
)*/
// }
onMounted(async () => {
switch (router.currentRoute.value.query.state) {
case 'sell':
isBuy.value = false
break
case 'buy':
isBuy.value = true
break
}
/* await initSellTable()
await initBuyTable()*/
})
const form = ref<Gold>({
goldCash: undefined,
goldValue: undefined,
percentWallet: undefined,
method: 'cash',
goldVazn:undefined,
gateway: false
})
const allowedAsciiCodeFloat = [46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57]
// const allowedAsciiCodeInt = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57]
watch(form.value, () => {
if (form.value.goldValue) {
form.value.goldValue = parseFloat(form.value.goldValue)
const lastCharGoldStr: string = form.value.goldValue.toString()
if (!allowedAsciiCodeFloat.includes(lastCharGoldStr.slice(-1).charCodeAt(0))) {
calculateCash(form.value.goldValue)
}
}
})
// Re-validate when gateway changes
watch(() => form.value.gateway, () => {
if (isBuy.value && form.value.goldValue) {
// Clear validation errors and re-validate
refInput.value?.clearValidate('goldValue')
setTimeout(() => {
refInput.value?.validateFields(['goldValue']).catch(() => {
// Ignore validation errors, just trigger re-validation
})
}, 100)
}
})
// Reset gateway when method changes to non-cash
watch(() => form.value.method, (newMethod) => {
if (newMethod !== 'cash' && form.value.gateway) {
form.value.gateway = false
}
})
onMounted(() => {
console.log(userStore.userInfo?.is_legal === 2)
if (userStore.userInfo?.is_legal === 0) {
noCompleteVisible.value = true
}
})
const productNum = ref<number>(0)
const getInventoryById = (id: number) => {
userStore?.productWallet?.forEach((val) => {
if (val.product_id == id)
productNum.value = val.gold
})
return productNum.value ?? 0
}
/*function percentChange(val: number) {
if (val !== 0)
if (isBuy.value) {
form.value.goldCash = unitCashCalculator.value * val
calculateGold(form.value.goldCash)
calculateCash(form.value.goldValue)
} else {
// console.log((unitGoldCalculator.value * val).toFixed(2))
// form.value.goldValue = parseFloat((unitGoldCalculator.value * val).toFixed(2))
form.value.goldValue = toFixedDownThreeChar(parseFloat(unitGoldCalculator.value * val))
calculateCash(form.value.goldValue)
}
else {
form.value.goldValue = undefined
form.value.goldCash = undefined
}
}*/
const unitGoldCalculator = computed(() => {
// console.log(walletStore?.walletInfo?.gold / 100)
return (walletStore?.walletInfo?.gold ?? 0) / 100
})
const unitCashCalculator = computed(() => {
return (walletStore?.walletInfo?.cash ?? 0) / 100
})
const limitedPrice = computed(() => {
if (walletStore.limitedPrice)
return walletStore?.limitedPrice[0]
})
function calculateCash(val: number) {
priceErrorVisible.value = false
// console.log(val)
if (price.value) {
form.value.goldVazn = parseFloat((val * (( props?.ProductItem?.price) + (props?.ProductItem?.cash_fee_buy)))/lastPrice.value?.buy).toFixed(1)
console.log(val,props?.ProductItem?.price,props?.ProductItem?.cash_fee_buy,lastPrice.value?.buy)
if (isBuy.value) {
if (val){
form.value.goldCash = parseFloat(val * (( props?.ProductItem?.price) + (props?.ProductItem?.cash_fee_buy))).toFixed(0)
}
else {
form.value.goldCash = 0
form.value.goldVazn = 0
}
}
else {
if (val)
form.value.goldCash = parseFloat((val * props?.ProductItem?.price) + (props?.ProductItem?.cash_fee_sell)).toFixed(0)
else{
form.value.goldCash = 0
form.value.goldVazn = 0
}
}
console.log(form.value.goldVazn)
}
}
/*function calculateGold(val: number) {
// console.log(val)
// form.value.goldCash = usePriceWithoutType(form.value.goldCash)
if (price.value) {
if (isBuy.value) {
if (val) {
form.value.goldValue = toFixedDownThreeChar(parseFloat(val / price?.value?.buy))
if (parseInt(finalPrice.value) > 0) priceErrorVisible.value = true
if (parseInt(finalPrice.value) === 0) priceErrorVisible.value = false
} else if (val.toString() === '') {
form.value.goldValue = undefined
priceErrorVisible.value = false
} else {
form.value.goldValue = 0
priceErrorVisible.value = false
}
} else {
if (val) {
form.value.goldValue = toFixedDownThreeChar(parseFloat((val / price?.value?.sell)))
if (parseInt(finalPrice.value) > 0) priceErrorVisible.value = true
if (parseInt(finalPrice.value) === 0) priceErrorVisible.value = false
} else if (val.toString() === '') {
// console.log('sasasasasasa')
form.value.goldValue = undefined
priceErrorVisible.value = false
} else {
form.value.goldValue = 0
priceErrorVisible.value = false
}
}
}
}*/
/*watch(form.value?.goldValue, (n)=>{
console.log('salammmm', n)
})*/
const bganim = ref(null)
function useStyle() {
if (isBuy.value) return 'right: 2%;'
// if (isbuy.value) return 'animation-direction: normal'
// else return 'animation-direction: reverse'
else return ' right: 98%; transform: translate(100%, 0);'
}
function setIsBuy(val: boolean) {
isBuy.value = val
form.value.goldValue = undefined
form.value.goldCash = undefined
form.value.percentWallet = 0
priceErrorVisible.value = false
// console.log(bganim.value.classList)
// bganim?.value?.classList.add(val?'right':'left')
// bganim?.value?.style.transform = `translateX(${val ? '0' : '-100'});`
// bganim?.value?.style.animationDirection = val? 'normal':'reverse'
// val? bganim?.value?.classList?.add("move") : bganim?.value?.classList?.remove("move")
// const el = bganim.value?.
// el.
}
const formatter = (value: number) => {
return `${value}%`
}
const price = computed((): Price => <Price>lastPrice.value ?? 0)
async function initPrice() {
try {
// chartLoading.value = true
await getLastPrices()
// await setIntervalGetPrice()
loading.value = true
price.value = lastPrice.value
props.ProductItem.price = lastPrice.value?.buy * props.ProductItem?.amount
props.ProductItem.sellPrice = lastPrice.value?.sell * props.ProductItem?.amount
} catch (e) {
console.log(e)
} finally {
loading.value = false
// chartLoading.value = false
}
}
async function okConfirm() {
try {
console.log('okConfirm called, form values:', form.value)
console.log('disableTransaction:', disableTransaction.value)
await refInput.value?.validateFields()
console.log('Validation passed, showing confirmation action sheet')
// If validation passes, show confirmation action sheet
confirmAction.value = true
console.log('confirmAction set to:', confirmAction.value)
} catch (error) {
// Validation failed, don't show action sheet
console.error('Validation failed:', error)
message.error('لطفا تمام فیلدها را به درستی پر کنید')
}
}
async function ok() {
console.log('ok() function called')
try {
loading.value = true
infoVisible.value = true
const data = await createOrder({
direction: isBuy.value ? 1 : 2,
price: isBuy.value ? price.value?.buy : price.value?.sell,
amount: form.value.goldValue ?? 0,
gateway: form.value.gateway,
product_id: props.ProductItem.id,
method: form.value.method,
number: form.value.goldValue ?? 0
})
// console.log('Order response:', data) // Debug log
// Check if response has returnUrl (for gateway payment)
let returnUrl = null
if (Array.isArray(data)) {
const itemWithReturnUrl = data.find(item => item?.returnUrl)
returnUrl = itemWithReturnUrl?.returnUrl
} else if (data?.returnUrl) {
returnUrl = data.returnUrl
}
console.log('Return URL:', returnUrl) // Debug log
// If returnUrl exists, redirect to gateway and stop execution
if (returnUrl) {
window.location.href = returnUrl
return // Stop execution here
}
// If no gateway, continue with normal flow
await walletStore.getMyWallet()
await router.push({ name: 'transactions' })
form.value = {
goldValue: undefined,
goldCash: undefined,
percentWallet: undefined,
method: 'cash',
gateway: false
}
// refreshTable()
message.success('سفارش شما با موفقیت انجام شد')
await fundStore.getMyFund()
} catch (e) {
// console.log(e)
// await initPrice()
} finally {
loading.value = false
infoVisible.value = false
}
}
</script>
<style lang="less">
/*.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}*/
/*.completeButtonStyle{
background: blue;
}*/
.completeButtonStyle .van-dialog__confirm .van-button__content {
background: #521620;
color: white;
}
.left {
transform: translateX(0);
left: 0;
}
.toggle-transe-parent {
animation: ease-in-out infinite alternate swingParent;
}
@keyframes swingParent {
from {
transform: translateX(0);
left: 0;
}
to {
left: 100%;
transform: translateX(-100%);
}
}
.animate.right {
right: 0;
}
.animate.right.move {
right: 100%;
transform: translate(100%, 0);
}
.text-secondary{
color: #d3a233 !important;
}
</style>
@@ -1,63 +1,156 @@
<template>
<div class="flex bg-white border-1 border-gray-200 rounded-lg"
:class="props.ProductItem.show ? 'bg-white opacity-100' : 'bg-gray-200 opacity-50'">
<div class="bg-gray-100 m-2 rounded-lg w-28 h-28 flex justify-center p-2">
<img v-if="props.ProductItem.group_type=='گوی'" :src="goyImg">
<img v-if="props.ProductItem.group_type=='مکعب'" :src="mokaabImg">
<img v-if="props.ProductItem.group_type=='سکه'" :src="sekkeImg">
<img v-if="props.ProductItem.group_type=='شمش ونوس'" :src="venusImg">
<img v-if="props.ProductItem.group_type=='سکه پارسیان'" :src="parsianImg">
<img v-if="props.ProductItem.group_type=='آبشده'|| props.ProductItem.group_type== null" :src="defaultImg">
</div>
<div class="m-1 flex flex-col gap-1 justify-center">
<div class="text-base font-500">{{ props.ProductItem.title }}</div>
<br>
<!--<template>-->
<!--<div class="flex bg-white border-1 border-gray-200 rounded-lg"-->
<!-- :class="props.ProductItem.show ? 'bg-white opacity-100' : 'bg-gray-200 opacity-50'">-->
<!-- <div class="bg-gray-100 m-2 rounded-lg w-28 h-28 flex justify-center p-2">-->
<!-- <img v-if="props.ProductItem.group_type=='گوی'" :src="goyImg">-->
<!-- <img v-if="props.ProductItem.group_type=='مکعب'" :src="mokaabImg">-->
<!-- <img v-if="props.ProductItem.group_type=='سکه'" :src="sekkeImg">-->
<!-- <img v-if="props.ProductItem.group_type=='شمش ونوس'" :src="venusImg">-->
<!-- <img v-if="props.ProductItem.group_type=='سکه پارسیان'" :src="parsianImg">-->
<!-- <img v-if="props.ProductItem.group_type=='آبشده'|| props.ProductItem.group_type== null" :src="defaultImg">-->
<!-- </div>-->
<!-- <div class="m-1 flex flex-col gap-1 justify-center">-->
<!-- <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 v-if="props.ProductItem.group_type!='آبشده'" class="text-xs text-gray-400">شامل 33 هزار تومان اجرت</div>-->
<!-- <div v-else class="text-xs text-gray-400">محاسبه بر اساس قیمت روز</div>-->
<!--&lt;!&ndash; <div v-if="props.ProductItem.group_type!='آبشده'" class=" font-500">{{ amountProduct}}</div>&ndash;&gt;-->
<!--&lt;!&ndash; <div v-if="props.ProductItem.group_type!='آبشده'" class="text-secondary font-500">{{ props.ProductItem.amount }}گرم</div>&ndash;&gt;-->
<!--&lt;!&ndash; <div v-else class="text-secondary font-500">حداقل خرید یک گرم</div>&ndash;&gt;-->
<!-- <div>{{usePrice((props.ProductItem.price ?? 0) + (props.ProductItem.cash_fee_buy ?? 0))}}</div>-->
<!--&lt;!&ndash; <div v-if="props.ProductItem.group_type!='آبشده'" class="text-xs text-gray-400">شامل 33 هزار تومان اجرت</div>&ndash;&gt;-->
<!--&lt;!&ndash; <div v-else class="text-xs text-gray-400">محاسبه بر اساس قیمت روز</div>&ndash;&gt;-->
<!-- </div>-->
<!--</div>-->
<!--</template>-->
<!--<script setup lang="ts">-->
<!--import defaultImg from "@/assets/img/defaultAbshodeh.svg"-->
<!--import goyImg from "@/assets/img/goyImg.svg"-->
<!--import mokaabImg from "@/assets/img/mokaabImg.svg"-->
<!--import sekkeImg from "@/assets/img/sekkeImg.svg"-->
<!--import parsianImg from "@/assets/img/parsianImg.svg"-->
<!--import type {Product} from "@/models/products";-->
<!--import {usePrice} from "../../../../utils/usePrice";-->
<!--import {computed, ref} from "vue";-->
<!--import venusImg from "@/assets/img/venousImg.svg"-->
<!--interface Prop {-->
<!-- ProductItem: Product-->
<!--}-->
<!--const amountProduct=computed(()=>{-->
<!-- if (0<props.ProductItem.amount && props.ProductItem.amount<1){-->
<!-- return `${props.ProductItem.amount*1000}سوت`-->
<!-- }else return `${props.ProductItem.amount}گرم`-->
<!--})-->
<!--const props = withDefaults(defineProps<Prop>(), {})-->
<!--// const typeOfProduct=(val:string)=>{-->
<!--// if (val.includes('گوی'))-->
<!--// return 'گوی'-->
<!--// if (val.includes('شمش'))-->
<!--// return 'شمش'-->
<!--// if (val.includes('مکعب'))-->
<!--// return 'مکعب'-->
<!--// if (val.includes('24 عیار'))-->
<!--// return '24 عیار'-->
<!--//-->
<!--// return 'آب شده'-->
<!--// }-->
<!--</script>-->
<!--<style scoped>-->
<!--</style>-->
<template>
<div
class="flex bg-white border border-gray-200 rounded-xl p-3 transition-all duration-300
hover:shadow-lg hover:-translate-y-0.5 cursor-pointer"
:class="!props.ProductItem.show && 'opacity-50 grayscale'"
>
<!-- Image -->
<div class="bg-gray-100 rounded-lg w-24 h-24 flex items-center justify-center shrink-0">
<img
:src="productImage"
class="max-h-30 object-contain"
alt=""
/>
</div>
<!-- Content -->
<div class="mr-4 flex flex-col justify-between flex-1">
<div>
<div class="text-sm font-semibold line-clamp-2 mb-1">
{{ props.ProductItem.title }}
</div>
<div class="text-xs text-gray-500">
{{ amountProduct }}
</div>
</div>
<div class="flex items-center justify-between mt-2">
<div class="!text-primary font-bold text-sm">
{{ usePrice((props.ProductItem.price ?? 0) + (props.ProductItem.cash_fee_buy ?? 0)) }}
</div>
<span
v-if="!props.ProductItem.show"
class="text-xs bg-gray-300 text-gray-600 px-2 py-0.5 rounded-md"
>
ناموجود
</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import defaultImg from "@/assets/img/defaultAbshodeh.svg"
import goyImg from "@/assets/img/goyImg.svg"
import mokaabImg from "@/assets/img/mokaabImg.svg"
import sekkeImg from "@/assets/img/sekkeImg.svg"
import parsianImg from "@/assets/img/parsianImg.svg"
import type {Product} from "@/models/products";
import {usePrice} from "../../../../utils/usePrice";
import {computed, ref} from "vue";
import venusImg from "@/assets/img/venousImg.svg"
import { computed } from 'vue'
import type { Product } from '@/models/products'
import { usePrice } from '@/utils/usePrice'
interface Prop {
import defaultImg from '@/assets/img/defaultAbshodeh.svg'
import goyImg from '@/assets/img/goyImg.svg'
import mokaabImg from '@/assets/img/mokaabImg.svg'
import sekkeImg from '@/assets/img/sekkeImg.svg'
import parsianImg from '@/assets/img/parsianImg.svg'
import venusImg from '@/assets/img/venousImg.svg'
interface Props {
ProductItem: Product
}
const amountProduct=computed(()=>{
if (0<props.ProductItem.amount && props.ProductItem.amount<1){
return `${props.ProductItem.amount*1000}سوت`
}else return `${props.ProductItem.amount}گرم`
})
const props = withDefaults(defineProps<Prop>(), {})
// const typeOfProduct=(val:string)=>{
// if (val.includes('گوی'))
// return 'گوی'
// if (val.includes('شمش'))
// return 'شمش'
// if (val.includes('مکعب'))
// return 'مکعب'
// if (val.includes('24 عیار'))
// return '24 عیار'
//
// return 'آب شده'
// }
const props = defineProps<Props>()
const amountProduct = computed(() => {
if (!props.ProductItem.amount) return ''
if (props.ProductItem.amount < 1) {
return `${props.ProductItem.amount * 1000} سوت`
}
return `${props.ProductItem.amount} گرم`
})
const productImage = computed(() => {
switch (props.ProductItem.group_type) {
case 'گوی':
return goyImg
case 'مکعب':
return mokaabImg
case 'سکه':
return sekkeImg
case 'شمش ونوس':
return venusImg
case 'سکه پارسیان':
return parsianImg
default:
return defaultImg
}
})
</script>
<style scoped>
</style>
.text-primary {
color: #d3a233;
}
</style>
@@ -0,0 +1,90 @@
<template>
<div
class="bg-white border border-gray-200 rounded-xl p-3 transition-all duration-300
hover:shadow-lg hover:-translate-y-1 cursor-pointer"
:class="!props.ProductItem.show && 'opacity-50 grayscale'"
>
<!-- Image -->
<div class="bg-gray-100 rounded-lg h-32 flex items-center justify-center mb-3">
<img
:src="productImage"
class="max-h-35 object-contain"
alt=""
/>
</div>
<!-- Title -->
<div class="text-sm font-semibold line-clamp-2 mb-2 min-h-[40px]">
{{ props.ProductItem.title }}
</div>
<!-- Amount -->
<div class="text-xs text-gray-500 mb-2">
{{ amountProduct }}
</div>
<!-- Price & Status -->
<div class="flex items-center justify-between">
<div class="!text-primary font-bold text-sm">
{{ usePrice((props.ProductItem.price ?? 0) + (props.ProductItem.cash_fee_buy ?? 0)) }}
</div>
<span
v-if="!props.ProductItem.show"
class="text-xs bg-gray-300 text-gray-600 px-2 py-0.5 rounded-md"
>
ناموجود
</span>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import type { Product } from '@/models/products'
import { usePrice } from '@/utils/usePrice'
import defaultImg from '@/assets/img/defaultAbshodeh.svg'
import goyImg from '@/assets/img/goyImg.svg'
import mokaabImg from '@/assets/img/mokaabImg.svg'
import sekkeImg from '@/assets/img/sekkeImg.svg'
import parsianImg from '@/assets/img/parsianImg.svg'
import venusImg from '@/assets/img/venousImg.svg'
interface Props {
ProductItem: Product
}
const props = defineProps<Props>()
const amountProduct = computed(() => {
if (!props.ProductItem.amount) return ''
if (props.ProductItem.amount < 1) {
return `${props.ProductItem.amount * 1000} سوت`
}
return `${props.ProductItem.amount} گرم`
})
const productImage = computed(() => {
switch (props.ProductItem.group_type) {
case 'گوی':
return goyImg
case 'مکعب':
return mokaabImg
case 'سکه':
return sekkeImg
case 'شمش ونوس':
return venusImg
case 'سکه پارسیان':
return parsianImg
default:
return defaultImg
}
})
</script>
<style scoped>
.text-primary {
color: #d3a233;
}
</style>
+76 -32
View File
@@ -12,11 +12,17 @@
<ARadioButton class="cats" v-for="item in typeItems" :value="item">{{ item }}</ARadioButton>
</ARadioGroup>
</div>
<div class="mx-5 flex justify-end" @click="viewVertical=!viewVertical">
<BasicIcon v-if="viewVertical" icon="vuesax-linear:row-vertical"></BasicIcon>
<BasicIcon v-else icon="vuesax-linear:element-3"></BasicIcon>
</div>
<div class="mx-5 pb-30 pt-2" style="color: #1b2b07">
<div class="mt-3" v-for="item in items">
<ProductItem :ProductItem="item" @click="clickProduct(item)"></ProductItem>
<div class="mx-5 pb-30 pt-2 grid grid-cols-2 gap-2" style="color: #1b2b07">
<div class="mt-3 gap-2 " :class="!viewVertical?'col-span-2':''" v-for="item in items">
<ProductItemVertical v-if="viewVertical" :ProductItem="item" @click="clickProduct(item)"></ProductItemVertical>
<ProductItem v-else class="col-span-2" :ProductItem="item" @click="clickProduct(item)"></ProductItem>
</div>
</div>
</div>
@@ -28,7 +34,7 @@
<BasicActionSheet placement="bottom" v-model:show="showFilter">
<div class="">
<indexGold v-if="selectedProduct?.group_type=='آبشده'" :ProductItem="selectedProduct"></indexGold>
<DealProduct v-else :ProductItem="selectedProduct"></DealProduct>
<DealProduct v-else :ProductItem="selectedProduct" @close="showFilter = false"></DealProduct>
</div>
</BasicActionSheet>
@@ -42,7 +48,8 @@ import { getProducts } from '@/api/products'
import {
getLastPrices,
lastPrice,
stopPriceInterval
stopPriceInterval,
setIntervalGetLastPrice
} from '@/utils/price'
import { loading, getToken } from '@/utils'
import { Price } from '@/models/home'
@@ -53,22 +60,20 @@ import DealProduct from '@/views/base/products/components/DealProduct.vue'
import { cloneDeep } from 'lodash'
import img from "@/assets/img/user_ly1.svg";
import { message } from 'ant-design-vue'
import BasicButton from "@/App.vue";
import BasicIcon from "@/components/BasicPacks/BasicIcon.vue";
import ProductItemVertical from "@/views/base/products/components/ProductItemVertical.vue";
const activekey = ref('آبشده')
const showFilter = ref()
const selectedProduct = ref<Product>()
const viewVertical=ref<boolean>('true')
function clickProduct(item: Product) {
if(item?.show){
// Check if user is logged in
if (!getToken()) {
message.warning('برای خرید یا فروش محصولات، لطفا ابتدا وارد حساب کاربری خود شوید')
router.push({ name: 'login' })
return
}
showFilter.value = true
selectedProduct.value = cloneDeep(item)
}
if(item?.show){
// Navigate to product detail page
router.push({ name: 'productDetail', params: { id: item.id } })
}
}
function changeTab() {
@@ -129,30 +134,69 @@ onUnmounted(() => {
// console.log('okgold')
})
// watch(lastPrice, async () => {
// // console.log(items);
// items.value?.forEach((val) => {
// // console.log(lastPrice.value)
// if (val.group_type == 'آبشده') {
// val.price = lastPrice.value?.price_last
// val.sellPrice = lastPrice.value?.sell
// } else {
// val.price = val.amount * lastPrice.value?.price_last
// val.sellPrice = val.amount * lastPrice.value?.sell
//
// // console.log(lastPrice.value?.price_last + "*" + val.amount + ' ' + val.price)
// }
//
// })
// })
watch(lastPrice, async () => {
// console.log(items);
if (lastPrice.value && items.value) {
items.value.forEach((val) => {
// console.log(lastPrice.value)
if (val.group_type == 'آبشده') {
val.price = lastPrice.value?.price_last
val.sellPrice = lastPrice.value?.sell
} else {
val.price = val.amount * lastPrice.value?.price_last
val.sellPrice = val.amount * lastPrice.value?.sell
// console.log(lastPrice.value?.price_last + "*" + val.amount + ' ' + val.price)
}
})
// Update firstItems as well
if (firstItems.value) {
firstItems.value.forEach((val) => {
if (val.group_type == 'آبشده') {
val.price = lastPrice.value?.price_last
val.sellPrice = lastPrice.value?.sell
} else {
val.price = val.amount * lastPrice.value?.price_last
val.sellPrice = val.amount * lastPrice.value?.sell
}
})
}
}
}, { deep: true })
async function initPrice() {
try {
// chartLoading.value = true
await getLastPrices()
// await setIntervalGetPrice()
await setIntervalGetLastPrice()
loading.value = true
price.value = lastPrice.value
// Update prices for all items
if (lastPrice.value && items.value) {
items.value.forEach((val) => {
if (val.group_type == 'آبشده') {
val.price = lastPrice.value?.price_last
val.sellPrice = lastPrice.value?.sell
} else {
val.price = val.amount * lastPrice.value?.price_last
val.sellPrice = val.amount * lastPrice.value?.sell
}
})
// Update firstItems as well
if (firstItems.value) {
firstItems.value.forEach((val) => {
if (val.group_type == 'آبشده') {
val.price = lastPrice.value?.price_last
val.sellPrice = lastPrice.value?.sell
} else {
val.price = val.amount * lastPrice.value?.price_last
val.sellPrice = val.amount * lastPrice.value?.sell
}
})
}
}
} catch (e: any) {
console.log(e)
// If 401 error, just skip price initialization
+260
View File
@@ -0,0 +1,260 @@
<template>
<div class="bottom-0 fixed top-0">
<img :src="img" alt="" class="w-inherit" :style="{ backgroundRepeat: 'repeat' }" />
</div>
<a-spin :spinning="loading">
<div class="!h-full overflow-y-scroll">
<div class="mx-5 pb-30 pt-5" style="color: #1b2b07">
<!-- Product Image -->
<div class="bg-gray-100 rounded-lg w-full flex justify-center p-8 mb-4">
<img v-if="product?.group_type == 'گوی'" :src="goyImg" class="max-w-full h-auto" />
<img v-else-if="product?.group_type == 'مکعب'" :src="mokaabImg" class="max-w-full h-auto" />
<img v-else-if="product?.group_type == 'سکه'" :src="sekkeImg" class="max-w-full h-auto" />
<img v-else-if="product?.group_type == 'شمش ونوس'" :src="venusImg" class="max-w-full h-auto" />
<img v-else-if="product?.group_type == 'سکه پارسیان'" :src="parsianImg" class="max-w-full h-auto" />
<img v-else :src="defaultImg" class="max-w-full h-auto" />
</div>
<!-- Product Title -->
<div class="text-2xl font-bold mb-4">{{ product?.title }}</div>
<div class=" flex items-center justify-between mb-4 gap-4">
<!-- Product Price -->
<div class="text-xl font-semibold text-primary">
{{ usePrice((product?.price ?? 0) + (product?.cash_fee_buy ?? 0)) }}
</div>
<div class="flex-1">
<!-- Buy Button -->
<BasicButton
v-if="product?.show && product?.open_buy"
class="w-full py-3 "
type="primary"
size="large"
@click="handleBuyClick"
>
خرید محصول
</BasicButton>
<div v-else-if="!product?.show" class="text-center text-gray-500 py-4">
این محصول در حال حاضر در دسترس نیست
</div>
<div v-else-if="!product?.open_buy" class="text-center text-gray-500 py-4">
خرید این محصول در حال حاضر امکان پذیر نیست
</div>
</div>
</div>
<!-- Product Description -->
<div v-if="!product?.description" class="mb-6">
<div class="text-lg font-semibold mb-2">توضیحات محصول:</div>
<!-- <div class="text-base text-gray-700 whitespace-pre-line">{{ product.description }}</div>-->
<div class="text-base text-gray-700 whitespace-pre-line">شمش طلای نقش برجسته ونوس ۱ گرمی انتخابی ظریف و ارزشمند برای سرمایه‌گذاری و هدیه است. این شمش از طلای خالص با عیار بالا ساخته شده و نقش برجسته‌ی الهه ونوس، نمادی از زیبایی و ظرافت، جلوه‌ای هنری و خاص به آن بخشیده است. وزن دقیق ۱ گرم، ابعاد مناسب و بسته‌بندی شکیل، این محصول را به گزینه‌ای ایده‌آل برای نگهداری ارزش، هدیه‌های خاص و کلکسیون‌های طلا تبدیل می‌کند. </div>
</div>
<!-- Similar Products -->
<div v-if="product?.similar?.length" class="mt-8">
<div class="text-lg font-semibold mb-4">محصولات مشابه</div>
<swiper
:slides-per-view="2.25"
:space-between="16"
class="similar-products-swiper"
>
<swiper-slide
v-for="item in product.similar"
:key="item.id"
>
<div
class="bg-white rounded-lg shadow p-4 cursor-pointer h-full"
@click="goToProduct(item.id)"
>
<!-- Image -->
<div class="bg-gray-100 rounded-md flex justify-center p-4 mb-2">
<img
:src="getProductImage(item.group_type)"
class="h-20 object-contain"
/>
</div>
<!-- Title -->
<div class="text-sm font-semibold mb-1 line-clamp-2">
{{ item.title }}
</div>
<!-- Price -->
<div class="text-sm text-primary font-bold">
{{ usePrice((item.price ?? 0) + (item.cash_fee_buy ?? 0)) }}
</div>
</div>
</swiper-slide>
</swiper>
</div>
</div>
</div>
</a-spin>
<!-- Basic Action Sheet for Buy -->
<BasicActionSheet placement="bottom" v-model:show="showFilter">
<div class="">
<indexGold v-if="product?.group_type == 'آبشده'" :ProductItem="product"></indexGold>
<DealProduct v-else :ProductItem="product" @close="showFilter = false"></DealProduct>
</div>
</BasicActionSheet>
</template>
<script setup lang="ts">
import {onMounted, onUnmounted, ref, computed, watch} from 'vue'
import { useRoute, useRouter } from 'vue-router'
import {getProductById, getProducts} from '@/api/products'
import { loading, getToken } from '@/utils'
import type { Product } from '@/models/products'
import BasicActionSheet from '@/components/BasicPacks/BasicActionSheet.vue'
import indexGold from '@/views/base/gold/index.vue'
import DealProduct from '@/views/base/products/components/DealProduct.vue'
import { cloneDeep } from 'lodash'
import img from '@/assets/img/user_ly1.svg'
import { message } from 'ant-design-vue'
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
import { usePrice } from '@/utils/usePrice'
import {
getLastPrices,
lastPrice,
stopPriceInterval
} from '@/utils/price'
import { Swiper, SwiperSlide } from 'swiper/vue'
import 'swiper/swiper-bundle.css'
// Image imports
import defaultImg from '@/assets/img/defaultAbshodeh.svg'
import goyImg from '@/assets/img/goyImg.svg'
import mokaabImg from '@/assets/img/mokaabImg.svg'
import sekkeImg from '@/assets/img/sekkeImg.svg'
import parsianImg from '@/assets/img/parsianImg.svg'
import venusImg from '@/assets/img/venousImg.svg'
const route = useRoute()
const router = useRouter()
const product = ref<Product>()
const showFilter = ref(false)
const productId = computed(() => {
const id = route.params.id
return typeof id === 'string' ? parseInt(id) : id
})
watch(
() => productId.value,
(newId, oldId) => {
if (newId && newId !== oldId) {
loadProduct()
window.scrollTo({ top: 0, behavior: 'smooth' })
}
}
)
async function loadProduct() {
try {
loading.value = true
// const products = await getProducts()
const products = await getProductById(Number(productId.value))
const foundProduct = products
if (!foundProduct) {
message.error('محصول یافت نشد')
router.push({ name: 'buyProduct' })
return
}
product.value = foundProduct
await initPrice()
} catch (e: any) {
console.log(e)
if (e?.response?.status === 401) {
message.warning('برای مشاهده محصولات، لطفا ابتدا وارد حساب کاربری خود شوید')
router.push({ name: 'login' })
} else {
message.error('خطا در بارگذاری محصول')
}
} finally {
loading.value = false
}
}
async function initPrice() {
try {
await getLastPrices()
loading.value = true
if (lastPrice.value && product.value) {
if (product.value.group_type == 'آبشده') {
product.value.price = lastPrice.value?.price_last
product.value.sellPrice = lastPrice.value?.sell
} else {
product.value.price = product.value.amount * lastPrice.value?.price_last
product.value.sellPrice = product.value.amount * lastPrice.value?.sell
}
}
} catch (e: any) {
console.log(e)
} finally {
loading.value = false
}
}
function handleBuyClick() {
if (!product.value?.show) {
return
}
// // Check if user is logged in
// if (!getToken()) {
// message.warning('برای خرید یا فروش محصولات، لطفا ابتدا وارد حساب کاربری خود شوید')
// router.push({ name: 'login' })
// return
// }
//
showFilter.value = true
product.value = cloneDeep(product.value)
}
onMounted(() => {
loadProduct()
})
onUnmounted(() => {
stopPriceInterval()
})
function goToProduct(id: number) {
router.push({ name: 'productDetail', params: { id } })
}
function getProductImage(type?: string) {
switch (type) {
case 'گوی':
return goyImg
case 'مکعب':
return mokaabImg
case 'سکه':
return sekkeImg
case 'شمش ونوس':
return venusImg
case 'سکه پارسیان':
return parsianImg
default:
return defaultImg
}
}
</script>
<style scoped>
.text-primary {
color: #d3a233;
}
.similar-products-swiper {
padding-bottom: 10px;
}
.similar-products-swiper :deep(.swiper-slide) {
height: auto;
}
</style>
+1
View File
@@ -14,6 +14,7 @@
<Login v-if="!enterType" @register="setEnterType"/>
<Register v-else @register="setEnterType"/>
</transition>
<div class="text-center text-primary font-500" @click="router.push({name:'buyProduct'})">بازگشت به فروشگاه</div>
</div>
</div>
</template>