Files
boursecu_user/src/views/base/piggy/index.vue
T
safavimohadeseh78andCursor 42aef88064 Port modular features into boursecu and fix desktop login UX.
Add support chat, piggy funds, PWA banner, notification/SSE sync, and messenger legacy guards while keeping boursecu theme; also balance login panels and unblock login navigation.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 20:14:46 +02:00

281 lines
8.7 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="piggy-page">
<div class="piggy-page__inner">
<header class="piggy-bar">
<button type="button" class="piggy-bar__btn" aria-label="بازگشت" @click="goBack">
<BasicIcon icon="vuesax-linear:arrow-right-1" size="22" />
</button>
<h1 class="piggy-bar__title">قلک‌ها</h1>
<button type="button" class="piggy-bar__btn piggy-bar__btn--primary" aria-label="قلک جدید" @click="goAdd">
<BasicIcon icon="vuesax-linear:add" size="20" />
</button>
</header>
<a-spin :spinning="busy">
<section class="piggy-summary">
<div class="piggy-summary__card">
<div class="piggy-summary__row">
<div class="min-w-0">
<p class="piggy-summary__label">جمع موجودی داخل قلک‌ها</p>
<p class="piggy-summary__value" dir="ltr">
{{ formatPiggyAmount(totalGold) }}
<span class="piggy-summary__unit">گرم</span>
</p>
</div>
<div class="piggy-summary__badge">
<BasicIcon icon="vuesax-linear:safe-home" size="22" />
</div>
</div>
<div class="piggy-summary__meta">
<span>{{ rows.length }} قلک</span>
<span class="piggy-summary__dot" />
<span>{{ groups.length }} محصول</span>
</div>
</div>
<BasicButton class="!w-full mt-3" type="primary" size="middle" block @click="goAdd">
<div class="flex items-center justify-center gap-2">
<BasicIcon icon="vuesax-linear:add-circle" size="18" />
<span>{{ rows.length ? 'افزودن قلک' : 'ساخت اولین قلک' }}</span>
</div>
</BasicButton>
</section>
<section class="piggy-body">
<div v-if="!rows.length" class="piggy-empty">
<div class="piggy-empty__icon">
<BasicIcon icon="vuesax-linear:safe-home" size="36" />
</div>
<p class="piggy-empty__title">قلکی ندارید</p>
<p class="piggy-empty__desc">
برای جدا کردن بخشی از موجودی هر محصول (مثلاً آبشده یا سکه) یک قلک بسازید.
</p>
</div>
<div v-else class="piggy-groups">
<div v-for="group in groups" :key="group.productId" class="piggy-group">
<div class="piggy-group__head">
<div class="min-w-0">
<p class="piggy-group__name">{{ group.productTitle }}</p>
<p class="piggy-group__count">{{ group.funds.length }} قلک</p>
</div>
<p class="piggy-group__total" dir="ltr">
{{ formatPiggyAmount(group.totalGold) }}
<span>گرم</span>
</p>
</div>
<div class="piggy-group__list">
<button
v-for="item in group.funds"
:key="item.id"
type="button"
class="piggy-row"
@click="openInfo(item.id!)"
>
<div class="piggy-row__icon">
<BasicIcon :icon="piggyIconName(item.title)" size="20" />
</div>
<div class="piggy-row__main">
<div class="piggy-row__title">{{ item.title || 'بدون عنوان' }}</div>
<div class="piggy-row__sub">کد {{ item.id }}</div>
</div>
<div class="piggy-row__amount" dir="ltr">
<strong>{{ formatPiggyAmount(fundWalletGold(item)) }}</strong>
<span>گرم</span>
</div>
<BasicIcon class="piggy-row__chevron" icon="vuesax-linear:arrow-left-1" size="16" />
</button>
</div>
</div>
</div>
</section>
</a-spin>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import router from '@/router'
import { useFundUserStore } from '@/store/fundStore'
import { useSettingsStore } from '@/store/settingsStore'
import { message } from 'ant-design-vue'
import type { Fund } from '@/models/fund'
import {
formatPiggyAmount,
fundWalletGold,
groupFundsByProduct,
piggyIconName,
} from './piggyIcons'
const fundStore = useFundUserStore()
const settingsStore = useSettingsStore()
const busy = ref(false)
const rows = computed(() => (fundStore.myPiggyFunds?.rows ?? []) as Fund[])
const groups = computed(() => groupFundsByProduct(rows.value))
const totalGold = computed(() => rows.value.reduce((sum, item) => sum + fundWalletGold(item), 0))
function goBack() {
router.back()
}
function goAdd() {
router.push({ name: 'piggy_add' })
}
function openInfo(id: number) {
router.push({ name: 'piggy_info', params: { id: String(id) } })
}
onMounted(async () => {
await settingsStore.fetchSettings()
if (!settingsStore.isSettingTruthy('enable_product_funds')) {
message.warning('قابلیت قلک غیرفعال است')
router.replace({ name: 'user' })
return
}
try {
busy.value = true
await fundStore.getMyPiggyFunds()
} finally {
busy.value = false
}
})
</script>
<style scoped>
.piggy-page {
box-sizing: border-box;
min-height: 100%;
width: 100%;
background: #f9fafb;
padding: 0 12px calc(var(--app-bottom-chrome-height, 108px) + 1rem);
}
.piggy-page__inner {
box-sizing: border-box;
width: 100%;
max-width: 26rem;
margin-inline: auto;
}
.piggy-bar {
@apply sticky top-0 z-20 flex items-center justify-between bg-white px-3 py-3;
box-shadow: 0 1px 0 rgba(15, 23, 42, 0.06);
}
.piggy-bar__title {
@apply m-0 truncate px-2 text-base font-bold text-gray-900;
}
.piggy-bar__btn {
@apply flex h-9 w-9 shrink-0 items-center justify-center rounded-full border-none bg-gray-100 text-gray-800;
}
.piggy-bar__btn--primary {
@apply bg-primary text-white;
}
.piggy-summary {
@apply px-3 pt-4 sm:px-4;
}
.piggy-summary__card {
@apply rounded-2xl bg-primary p-4 text-white;
}
.piggy-summary__row {
@apply flex items-start justify-between gap-3;
}
.piggy-summary__label {
@apply m-0 text-sm text-white/80;
}
.piggy-summary__value {
@apply m-0 mt-1 flex flex-wrap items-baseline gap-2 text-2xl font-bold sm:text-3xl;
}
.piggy-summary__unit {
@apply text-sm font-medium text-white/75;
}
.piggy-summary__badge {
@apply flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-white/15;
}
.piggy-summary__meta {
@apply mt-4 flex flex-wrap items-center gap-2 text-xs text-white/75;
}
.piggy-summary__dot {
@apply h-1 w-1 rounded-full bg-white/50;
}
.piggy-body {
@apply px-3 pt-4 sm:px-4;
}
.piggy-groups {
@apply flex flex-col gap-4;
}
.piggy-group {
@apply overflow-hidden rounded-2xl bg-white;
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
}
.piggy-group__head {
@apply flex items-center justify-between gap-3 border-0 border-b border-solid border-gray-100 px-3 py-3 sm:px-4;
}
.piggy-group__name {
@apply m-0 truncate text-sm font-bold text-gray-900;
}
.piggy-group__count {
@apply m-0 mt-0.5 text-[11px] text-gray-500;
}
.piggy-group__total {
@apply m-0 flex shrink-0 items-baseline gap-1 text-sm font-bold text-primary;
}
.piggy-group__total span {
@apply text-[11px] font-medium text-gray-500;
}
.piggy-group__list {
@apply flex flex-col;
}
.piggy-row {
@apply flex w-full min-w-0 items-center gap-2 border-0 border-b border-solid border-gray-50 bg-transparent px-2.5 py-3 text-right last:border-b-0 sm:gap-3 sm:px-3;
}
.piggy-row__icon {
@apply flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-gray-50 text-primary sm:h-10 sm:w-10;
}
.piggy-row__main {
@apply min-w-0 flex-1;
}
.piggy-row__title {
@apply truncate text-sm font-bold text-gray-900;
}
.piggy-row__sub {
@apply mt-0.5 text-[11px] text-gray-400;
}
.piggy-row__amount {
@apply flex shrink-0 flex-col items-end leading-tight;
}
.piggy-row__amount strong {
@apply text-sm font-bold text-gray-900;
}
.piggy-row__amount span {
@apply text-[11px] text-gray-500;
}
.piggy-row__chevron {
@apply shrink-0 text-gray-300;
}
.piggy-empty {
@apply rounded-2xl bg-white px-5 py-10 text-center;
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
}
.piggy-empty__icon {
@apply mx-auto mb-3 flex h-16 w-16 items-center justify-center rounded-2xl bg-gray-50 text-primary;
}
.piggy-empty__title {
@apply m-0 text-base font-bold text-gray-900;
}
.piggy-empty__desc {
@apply m-0 mt-2 text-sm leading-6 text-gray-500;
}
@media (min-width: 768px) {
.piggy-page {
padding: 16px 16px 2rem;
}
.piggy-page__inner {
max-width: 28rem;
}
.piggy-bar {
border-radius: 12px;
}
}
</style>