change transition structure 2
This commit is contained in:
@@ -0,0 +1,503 @@
|
||||
<template>
|
||||
<section class="dashboard-panel dashboard-profit-split">
|
||||
<div class="mb-2 flex items-start justify-between gap-2">
|
||||
<DashboardSectionHeader
|
||||
class="!mb-0 min-w-0 flex-1"
|
||||
title="سود طلایی و ریالی"
|
||||
help="کارمزد معاملات بر اساس فیلدی که مشتری وارد کرده تفکیک میشود: وقتی مشتری مبلغ ریالی ثابت میزند کارمزد از طلا کسر میشود (سود طلایی)، و وقتی وزن ثابت میزند کارمزد از ریال کسر میشود (سود ریالی)."
|
||||
/>
|
||||
<a-button type="primary" size="small" @click="openSanad">
|
||||
ثبت سند تبدیل سود به طلا
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<!-- دو کارت خلاصه -->
|
||||
<div class="dashboard-profit-split__cards">
|
||||
<article class="dashboard-profit-split__card dashboard-profit-split__card--gold">
|
||||
<span class="dashboard-profit-split__card-title">سود طلایی</span>
|
||||
<p class="dashboard-profit-split__card-hint">کارمزد کسرشده از طلا در این بازه</p>
|
||||
<p class="dashboard-profit-split__value">
|
||||
<span class="dashboard-profit-split__amount">{{ formatGram(profitGoldGrams) }}</span>
|
||||
<span class="dashboard-profit-split__unit">گرم</span>
|
||||
</p>
|
||||
<p class="dashboard-profit-split__sub">
|
||||
≈ {{ formatDashboardAmount(profitGoldValueRial) }} ریال
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="dashboard-profit-split__card dashboard-profit-split__card--rial">
|
||||
<span class="dashboard-profit-split__card-title">سود ریالی</span>
|
||||
<p class="dashboard-profit-split__card-hint">کارمزد کسرشده از ریال در این بازه</p>
|
||||
<p class="dashboard-profit-split__value">
|
||||
<span class="dashboard-profit-split__amount">{{ formatDashboardAmount(profitRial) }}</span>
|
||||
<span class="dashboard-profit-split__unit">ریال</span>
|
||||
</p>
|
||||
<p class="dashboard-profit-split__sub">
|
||||
{{ dailyRows.length }} روز دارای معامله
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<!-- جدول روزانه -->
|
||||
<div class="mt-3 overflow-x-auto">
|
||||
<table class="dashboard-table w-full min-w-[34rem] text-sm">
|
||||
<thead>
|
||||
<tr class="border-b text-xs">
|
||||
<th class="px-2 py-2 text-right">تاریخ</th>
|
||||
<th class="px-2 py-2 text-right">سود طلایی (گرم)</th>
|
||||
<th class="px-2 py-2 text-right">سود ریالی (ریال)</th>
|
||||
<th class="px-2 py-2 text-right">سود اختلاف قیمت (ریال)</th>
|
||||
<th class="px-2 py-2 text-right">تعداد سفارش</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="!dailyRows.length">
|
||||
<td colspan="5" class="dashboard-text-muted px-2 py-4 text-center text-xs">
|
||||
در این بازه معاملهای ثبت نشده
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-for="row in dailyRows"
|
||||
:key="row.date"
|
||||
class="border-b hover:bg-[var(--admin-bg-hover)]"
|
||||
>
|
||||
<td class="whitespace-nowrap px-2 py-2 text-xs">{{ jalaliDate(row.date) }}</td>
|
||||
<td class="px-2 py-2 font-semibold" :class="row.profit_gold > 0 ? 'dashboard-profit' : ''">
|
||||
{{ formatGram(row.profit_gold) }}
|
||||
</td>
|
||||
<td class="px-2 py-2 font-semibold" :class="row.profit_rial > 0 ? 'dashboard-profit' : ''">
|
||||
{{ formatDashboardAmount(row.profit_rial) }}
|
||||
</td>
|
||||
<td class="px-2 py-2">{{ formatDashboardAmount(row.spread_rial) }}</td>
|
||||
<td class="px-2 py-2">{{ Number(row.order_count).toLocaleString('fa-IR') }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- سندهای تبدیل سود به طلا -->
|
||||
<div class="dashboard-profit-split__sanad mt-3">
|
||||
<div class="mb-2 flex flex-wrap items-center justify-between gap-2">
|
||||
<span class="text-sm font-bold">سندهای تبدیل سود ریالی به طلا</span>
|
||||
<span v-if="conversions.length" class="dashboard-text-muted text-xs">
|
||||
جمع تبدیلشده: {{ formatDashboardAmount(conversionsTotalRial) }} ریال →
|
||||
{{ formatGram(conversionsTotalGram) }} گرم
|
||||
</span>
|
||||
</div>
|
||||
<a-spin :spinning="conversionsLoading">
|
||||
<div v-if="!conversions.length" class="dashboard-empty py-3 text-center text-xs">
|
||||
هنوز سندی ثبت نشده
|
||||
</div>
|
||||
<ul v-else class="dashboard-profit-split__sanad-list">
|
||||
<li
|
||||
v-for="item in conversions"
|
||||
:key="item.id"
|
||||
class="dashboard-profit-split__sanad-item"
|
||||
>
|
||||
<div class="min-w-0">
|
||||
<div class="flex flex-wrap items-center gap-x-2 gap-y-0.5 text-sm">
|
||||
<span class="font-bold">{{ formatDashboardAmount(item.rial_amount) }} ریال</span>
|
||||
<span class="dashboard-text-muted">←</span>
|
||||
<span class="font-bold dashboard-profit">{{ formatGram(item.gram_amount) }} گرم</span>
|
||||
<span class="dashboard-text-muted text-xs">
|
||||
(مظنه {{ formatDashboardAmount(item.unit_price) }})
|
||||
</span>
|
||||
</div>
|
||||
<div class="dashboard-text-muted mt-0.5 text-[11px]">
|
||||
{{ jalaliDate(item.converted_at) }}
|
||||
<template v-if="item.description">— {{ item.description }}</template>
|
||||
</div>
|
||||
</div>
|
||||
<a-popconfirm
|
||||
title="این سند حذف شود؟"
|
||||
ok-text="حذف"
|
||||
cancel-text="انصراف"
|
||||
@confirm="removeConversion(item.id)"
|
||||
>
|
||||
<a-button type="text" danger size="small">حذف</a-button>
|
||||
</a-popconfirm>
|
||||
</li>
|
||||
</ul>
|
||||
</a-spin>
|
||||
</div>
|
||||
|
||||
<!-- مدال ثبت سند -->
|
||||
<BasicActionSheet
|
||||
v-model:visible="sanadVisible"
|
||||
title="ثبت سند تبدیل سود به طلا"
|
||||
modal-width="24rem"
|
||||
drawer-height="auto"
|
||||
compact
|
||||
:show-button="false"
|
||||
@close="resetForm"
|
||||
>
|
||||
<div class="dashboard-profit-split__form">
|
||||
<div class="dashboard-profit-split__field">
|
||||
<label>مبلغ ریالی سود</label>
|
||||
<a-input-number
|
||||
v-model:value="form.rial_amount"
|
||||
:min="0"
|
||||
:step="1000000"
|
||||
class="w-full"
|
||||
:formatter="numFormatter"
|
||||
:parser="numParser"
|
||||
@change="syncFromRial"
|
||||
/>
|
||||
</div>
|
||||
<div class="dashboard-profit-split__field">
|
||||
<label>قیمت هر گرم (مظنه)</label>
|
||||
<a-input-number
|
||||
v-model:value="form.unit_price"
|
||||
:min="0"
|
||||
:step="1000000"
|
||||
class="w-full"
|
||||
:formatter="numFormatter"
|
||||
:parser="numParser"
|
||||
@change="syncFromRial"
|
||||
/>
|
||||
</div>
|
||||
<div class="dashboard-profit-split__field">
|
||||
<label>وزن معادل (گرم)</label>
|
||||
<a-input-number
|
||||
v-model:value="form.gram_amount"
|
||||
:min="0"
|
||||
:step="0.1"
|
||||
class="w-full"
|
||||
@change="syncFromGram"
|
||||
/>
|
||||
</div>
|
||||
<div class="dashboard-profit-split__field">
|
||||
<label>تاریخ سند</label>
|
||||
<BaseDatePicker
|
||||
v-model:value="form.converted_at"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
<div class="dashboard-profit-split__field">
|
||||
<label>توضیحات</label>
|
||||
<a-textarea
|
||||
v-model:value="form.description"
|
||||
:rows="2"
|
||||
placeholder="مثلاً: از فلانجا ۲ گرم طلا خریده شد"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-profit-split__form-footer">
|
||||
<a-button danger block @click="closeSanad">بستن</a-button>
|
||||
<a-button type="primary" block :loading="submitting" @click="submitSanad">ثبت سند</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</BasicActionSheet>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import moment from 'moment-jalaali'
|
||||
import { message } from 'ant-design-vue'
|
||||
import DashboardSectionHeader from '@/pages/dashboard/components/DashboardSectionHeader.vue'
|
||||
import BasicActionSheet from '@/components/BasicActionSheet.vue'
|
||||
import { formatDashboardAmount } from '@/pages/dashboard/dashboardFormat'
|
||||
import {
|
||||
deleteProfitConversion,
|
||||
getProfitConversions,
|
||||
storeProfitConversion,
|
||||
} from '@/api/dashboard'
|
||||
import type { DashboardFinance, ProfitConversion } from '@/model/dashboard'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
|
||||
const props = defineProps<{
|
||||
finance?: DashboardFinance | null
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
/** قیمت هر گرم برای مقدار پیشفرض سند */
|
||||
unitPrice?: number
|
||||
}>()
|
||||
|
||||
const profitGoldGrams = computed(() => Number(props.finance?.profitGoldGrams ?? 0))
|
||||
const profitRial = computed(() => Number(props.finance?.profitRial ?? 0))
|
||||
const profitGoldValueRial = computed(() => Number(props.finance?.profitGoldValueRial ?? 0))
|
||||
const dailyRows = computed(() => props.finance?.daily ?? [])
|
||||
|
||||
const conversions = ref<ProfitConversion[]>([])
|
||||
const conversionsLoading = ref(false)
|
||||
const conversionsTotalRial = ref(0)
|
||||
const conversionsTotalGram = ref(0)
|
||||
|
||||
const sanadVisible = ref(false)
|
||||
const submitting = ref(false)
|
||||
|
||||
type SanadForm = {
|
||||
rial_amount: number | null
|
||||
gram_amount: number | null
|
||||
unit_price: number | null
|
||||
converted_at: string | null
|
||||
description: string | null
|
||||
}
|
||||
|
||||
function emptyForm(): SanadForm {
|
||||
return {
|
||||
rial_amount: null,
|
||||
gram_amount: null,
|
||||
unit_price: props.unitPrice ? Number(props.unitPrice) : null,
|
||||
converted_at: moment().format('YYYY-MM-DD HH:mm:ss'),
|
||||
description: null,
|
||||
}
|
||||
}
|
||||
|
||||
const form = ref<SanadForm>(emptyForm())
|
||||
|
||||
function formatGram(value?: number | null) {
|
||||
return Number(value ?? 0).toLocaleString('fa-IR', { maximumFractionDigits: 4 })
|
||||
}
|
||||
|
||||
function jalaliDate(value?: string | null) {
|
||||
if (!value) return '—'
|
||||
const normalized = String(value).length <= 10 ? `${value} 12:00:00` : value
|
||||
return toJalali(normalized, 'jYYYY/jMM/jDD')
|
||||
}
|
||||
|
||||
function numFormatter(value: string | number) {
|
||||
return String(value ?? '').replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||
}
|
||||
function numParser(value: string) {
|
||||
return value.replace(/[^\d.]/g, '')
|
||||
}
|
||||
|
||||
function syncFromRial() {
|
||||
const rial = Number(form.value.rial_amount ?? 0)
|
||||
const unit = Number(form.value.unit_price ?? 0)
|
||||
if (rial > 0 && unit > 0) {
|
||||
form.value.gram_amount = Number((rial / unit).toFixed(4))
|
||||
}
|
||||
}
|
||||
|
||||
function syncFromGram() {
|
||||
const gram = Number(form.value.gram_amount ?? 0)
|
||||
const unit = Number(form.value.unit_price ?? 0)
|
||||
if (gram > 0 && unit > 0) {
|
||||
form.value.rial_amount = Math.round(gram * unit)
|
||||
}
|
||||
}
|
||||
|
||||
async function loadConversions() {
|
||||
conversionsLoading.value = true
|
||||
try {
|
||||
const res = await getProfitConversions(props.startDate, props.endDate)
|
||||
conversions.value = res?.items ?? []
|
||||
conversionsTotalRial.value = Number(res?.totalRial ?? 0)
|
||||
conversionsTotalGram.value = Number(res?.totalGram ?? 0)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
conversionsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openSanad() {
|
||||
form.value = emptyForm()
|
||||
sanadVisible.value = true
|
||||
}
|
||||
|
||||
function closeSanad() {
|
||||
sanadVisible.value = false
|
||||
resetForm()
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
form.value = emptyForm()
|
||||
}
|
||||
|
||||
async function submitSanad() {
|
||||
const rial = Number(form.value.rial_amount ?? 0)
|
||||
const gram = Number(form.value.gram_amount ?? 0)
|
||||
if (rial <= 0 && gram <= 0) {
|
||||
message.warning('مبلغ ریالی یا وزن طلا را وارد کنید')
|
||||
return
|
||||
}
|
||||
submitting.value = true
|
||||
try {
|
||||
await storeProfitConversion({
|
||||
rial_amount: form.value.rial_amount,
|
||||
gram_amount: form.value.gram_amount,
|
||||
unit_price: form.value.unit_price,
|
||||
converted_at: form.value.converted_at,
|
||||
description: form.value.description,
|
||||
})
|
||||
message.success('سند تبدیل سود ثبت شد')
|
||||
closeSanad()
|
||||
await loadConversions()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
message.error('خطا در ثبت سند')
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function removeConversion(id: number) {
|
||||
try {
|
||||
await deleteProfitConversion(id)
|
||||
message.success('سند حذف شد')
|
||||
await loadConversions()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
message.error('خطا در حذف سند')
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [props.startDate, props.endDate],
|
||||
() => {
|
||||
void loadConversions()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.dashboard-profit-split {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.dashboard-profit-split__cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.dashboard-profit-split__cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-profit-split__card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
min-height: 5rem;
|
||||
padding: 0.6rem 0.75rem;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--admin-border);
|
||||
background: var(--admin-bg-muted);
|
||||
}
|
||||
|
||||
.dashboard-profit-split__card--gold {
|
||||
border-color: rgba(245, 158, 11, 0.4);
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
}
|
||||
|
||||
.dashboard-profit-split__card--rial {
|
||||
border-color: rgba(14, 165, 233, 0.35);
|
||||
background: rgba(14, 165, 233, 0.1);
|
||||
}
|
||||
|
||||
.dashboard-profit-split__card-title {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 700;
|
||||
color: var(--admin-text);
|
||||
}
|
||||
|
||||
.dashboard-profit-split__card-hint {
|
||||
margin: 0;
|
||||
font-size: 0.625rem;
|
||||
color: var(--admin-text-secondary);
|
||||
}
|
||||
|
||||
.dashboard-profit-split__value {
|
||||
margin: 0.15rem 0 0;
|
||||
direction: ltr;
|
||||
text-align: right;
|
||||
unicode-bidi: isolate;
|
||||
}
|
||||
|
||||
.dashboard-profit-split__amount {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 800;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.dashboard-profit-split__unit {
|
||||
margin-right: 3px;
|
||||
font-size: 0.625rem;
|
||||
font-weight: 600;
|
||||
color: var(--admin-text-secondary);
|
||||
}
|
||||
|
||||
.dashboard-profit-split__sub {
|
||||
margin: 0;
|
||||
font-size: 0.6875rem;
|
||||
color: var(--admin-text-secondary);
|
||||
}
|
||||
|
||||
.dashboard-profit-split__sanad {
|
||||
border-top: 1px solid var(--admin-border);
|
||||
padding-top: 0.75rem;
|
||||
}
|
||||
|
||||
.dashboard-profit-split__sanad-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.dashboard-profit-split__sanad-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0.65rem;
|
||||
border-radius: 0.5rem;
|
||||
background: var(--admin-bg-muted);
|
||||
border: 1px solid var(--admin-border);
|
||||
}
|
||||
|
||||
.dashboard-profit-split__form {
|
||||
@apply flex flex-col gap-3 px-4 pb-2 pt-1;
|
||||
}
|
||||
|
||||
.dashboard-profit-split__field {
|
||||
@apply flex flex-col gap-1;
|
||||
}
|
||||
|
||||
.dashboard-profit-split__field label {
|
||||
@apply text-xs font-semibold text-slate-600 dark:text-slate-300;
|
||||
}
|
||||
|
||||
.dashboard-profit-split__form-footer {
|
||||
@apply sticky bottom-0 grid grid-cols-2 gap-2 border-t border-slate-200 bg-white pt-3 dark:border-slate-600 dark:bg-slate-900;
|
||||
}
|
||||
|
||||
.dashboard-text-muted {
|
||||
color: var(--admin-text-secondary);
|
||||
}
|
||||
|
||||
.dashboard-empty {
|
||||
color: var(--admin-text-secondary);
|
||||
}
|
||||
|
||||
.dashboard-profit {
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
html.is-dark {
|
||||
.dashboard-profit {
|
||||
color: #6ee7b7;
|
||||
}
|
||||
|
||||
.dashboard-profit-split__card--gold {
|
||||
border-color: rgba(245, 158, 11, 0.45);
|
||||
background: rgba(245, 158, 11, 0.14);
|
||||
}
|
||||
|
||||
.dashboard-profit-split__card--rial {
|
||||
border-color: rgba(14, 165, 233, 0.4);
|
||||
background: rgba(14, 165, 233, 0.14);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -47,6 +47,13 @@
|
||||
|
||||
<DashboardTradingIncomeSummary :finance="finance" />
|
||||
|
||||
<DashboardProfitSplit
|
||||
:finance="finance"
|
||||
:start-date="startDate"
|
||||
:end-date="endDate"
|
||||
:unit-price="master?.price"
|
||||
/>
|
||||
|
||||
<!-- تراز و آمار — ۴ کارت در یک ردیف -->
|
||||
<section class="dashboard-metrics-row mt-2">
|
||||
<DashboardStatCard
|
||||
@@ -446,6 +453,7 @@ import DashboardQuickWalletModal from '@/pages/dashboard/components/DashboardQui
|
||||
import DashboardSectionHeader from '@/pages/dashboard/components/DashboardSectionHeader.vue'
|
||||
import DashboardStatCard from '@/pages/dashboard/components/DashboardStatCard.vue'
|
||||
import DashboardTradingIncomeSummary from '@/pages/dashboard/components/DashboardTradingIncomeSummary.vue'
|
||||
import DashboardProfitSplit from '@/pages/dashboard/components/DashboardProfitSplit.vue'
|
||||
import {
|
||||
dashboardCharts,
|
||||
dashboardCustomerMetrics,
|
||||
|
||||
Reference in New Issue
Block a user