2026-10-18 18:49:49 +03:30
|
|
|
|
<script setup lang="ts">
|
2026-11-03 16:51:51 +03:30
|
|
|
|
import { computed, onMounted, ref } from 'vue';
|
2026-10-18 21:06:18 +03:30
|
|
|
|
import {
|
|
|
|
|
|
Alert as AAlert,
|
2026-11-03 16:51:51 +03:30
|
|
|
|
Avatar as AAvatar,
|
2026-10-18 21:06:18 +03:30
|
|
|
|
Card as ACard,
|
|
|
|
|
|
Col as ACol,
|
|
|
|
|
|
Empty as AEmpty,
|
|
|
|
|
|
Flex as AFlex,
|
|
|
|
|
|
Row as ARow,
|
2026-11-03 16:51:51 +03:30
|
|
|
|
Spin as ASpin,
|
2026-10-18 21:06:18 +03:30
|
|
|
|
Typography,
|
2026-11-03 16:51:51 +03:30
|
|
|
|
theme,
|
2026-10-18 21:06:18 +03:30
|
|
|
|
} from 'ant-design-vue';
|
2026-11-03 16:51:51 +03:30
|
|
|
|
import type { Component } from 'vue';
|
|
|
|
|
|
import {
|
|
|
|
|
|
AccountBookOutlined,
|
|
|
|
|
|
ArrowDownOutlined,
|
|
|
|
|
|
ArrowUpOutlined,
|
|
|
|
|
|
BankOutlined,
|
|
|
|
|
|
CalendarOutlined,
|
|
|
|
|
|
FallOutlined,
|
|
|
|
|
|
RiseOutlined,
|
|
|
|
|
|
WalletOutlined,
|
|
|
|
|
|
} from '@ant-design/icons-vue';
|
|
|
|
|
|
import { use } from 'echarts/core';
|
|
|
|
|
|
import { CanvasRenderer } from 'echarts/renderers';
|
|
|
|
|
|
import { BarChart, PieChart } from 'echarts/charts';
|
|
|
|
|
|
import {
|
|
|
|
|
|
GridComponent,
|
|
|
|
|
|
LegendComponent,
|
|
|
|
|
|
TooltipComponent,
|
|
|
|
|
|
} from 'echarts/components';
|
|
|
|
|
|
import type { ComposeOption } from 'echarts/core';
|
|
|
|
|
|
import type { BarSeriesOption, PieSeriesOption } from 'echarts/charts';
|
|
|
|
|
|
import type {
|
|
|
|
|
|
GridComponentOption,
|
|
|
|
|
|
LegendComponentOption,
|
|
|
|
|
|
TooltipComponentOption,
|
|
|
|
|
|
} from 'echarts/components';
|
|
|
|
|
|
import VChart from 'vue-echarts';
|
2026-10-18 18:49:49 +03:30
|
|
|
|
import { api } from '@/lib/api';
|
|
|
|
|
|
import type { TreasuryDashboardDto } from '@mento/shared';
|
|
|
|
|
|
import TreasuryNav from '@/components/treasury/TreasuryNav.vue';
|
2026-11-03 16:51:51 +03:30
|
|
|
|
import {
|
|
|
|
|
|
TREASURY_DASHBOARD_MOCK,
|
|
|
|
|
|
isTreasuryDashboardEmpty,
|
|
|
|
|
|
} from './treasury-dashboard-mock';
|
2026-10-18 18:49:49 +03:30
|
|
|
|
import { toman } from './treasury-format';
|
|
|
|
|
|
|
2026-11-03 16:51:51 +03:30
|
|
|
|
use([CanvasRenderer, BarChart, PieChart, GridComponent, TooltipComponent, LegendComponent]);
|
|
|
|
|
|
|
|
|
|
|
|
type ChartOption = ComposeOption<
|
|
|
|
|
|
| BarSeriesOption
|
|
|
|
|
|
| PieSeriesOption
|
|
|
|
|
|
| GridComponentOption
|
|
|
|
|
|
| TooltipComponentOption
|
|
|
|
|
|
| LegendComponentOption
|
|
|
|
|
|
>;
|
|
|
|
|
|
|
2026-10-18 21:06:18 +03:30
|
|
|
|
const { Title: ATypographyTitle, Text: ATypographyText } = Typography;
|
2026-11-03 16:51:51 +03:30
|
|
|
|
const { token } = theme.useToken();
|
|
|
|
|
|
|
|
|
|
|
|
function num(value: string | number | null | undefined) {
|
|
|
|
|
|
const n = Number(value ?? 0);
|
|
|
|
|
|
return Number.isFinite(n) ? n : 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type MetricKey = keyof Pick<
|
|
|
|
|
|
TreasuryDashboardDto,
|
|
|
|
|
|
| 'bookedIncome'
|
|
|
|
|
|
| 'actualReceipts'
|
|
|
|
|
|
| 'bookedExpense'
|
|
|
|
|
|
| 'actualPayments'
|
|
|
|
|
|
| 'operatingProfit'
|
|
|
|
|
|
| 'receivables'
|
|
|
|
|
|
| 'payables'
|
|
|
|
|
|
| 'totalBalance'
|
|
|
|
|
|
>;
|
|
|
|
|
|
|
|
|
|
|
|
type MetricCard = {
|
|
|
|
|
|
key: MetricKey;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
hint: string;
|
|
|
|
|
|
icon: Component;
|
|
|
|
|
|
};
|
2026-10-18 21:06:18 +03:30
|
|
|
|
|
2026-10-18 18:49:49 +03:30
|
|
|
|
const data = ref<TreasuryDashboardDto | null>(null);
|
|
|
|
|
|
const error = ref('');
|
2026-11-03 16:51:51 +03:30
|
|
|
|
const loading = ref(true);
|
|
|
|
|
|
const usingMock = ref(false);
|
2026-10-18 18:49:49 +03:30
|
|
|
|
|
2026-11-03 16:51:51 +03:30
|
|
|
|
const heroCards: MetricCard[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'totalBalance',
|
|
|
|
|
|
label: 'موجودی حسابها',
|
|
|
|
|
|
hint: 'صندوق و بانک',
|
|
|
|
|
|
icon: WalletOutlined,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'operatingProfit',
|
|
|
|
|
|
label: 'سود یا زیان عملیاتی',
|
|
|
|
|
|
hint: 'درآمد ثبتشده − هزینه ثبتشده',
|
|
|
|
|
|
icon: RiseOutlined,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'receivables',
|
|
|
|
|
|
label: 'مطالبات وصولنشده',
|
|
|
|
|
|
hint: 'درآمد بدون دریافت کامل',
|
|
|
|
|
|
icon: CalendarOutlined,
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const secondaryCards: MetricCard[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'bookedIncome',
|
|
|
|
|
|
label: 'درآمد ثبتشده',
|
|
|
|
|
|
hint: 'تعهدی',
|
|
|
|
|
|
icon: ArrowDownOutlined,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'actualReceipts',
|
|
|
|
|
|
label: 'دریافت واقعی',
|
|
|
|
|
|
hint: 'ورود به حساب',
|
|
|
|
|
|
icon: BankOutlined,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'bookedExpense',
|
|
|
|
|
|
label: 'هزینه ثبتشده',
|
|
|
|
|
|
hint: 'تعهدی',
|
|
|
|
|
|
icon: FallOutlined,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'actualPayments',
|
|
|
|
|
|
label: 'پرداخت واقعی',
|
|
|
|
|
|
hint: 'خروج از حساب',
|
|
|
|
|
|
icon: ArrowUpOutlined,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'payables',
|
|
|
|
|
|
label: 'تعهدات پرداخت',
|
|
|
|
|
|
hint: 'هزینه بدون پرداخت کامل',
|
|
|
|
|
|
icon: AccountBookOutlined,
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const softCardStyle = computed(() => ({
|
|
|
|
|
|
background: token.value.colorPrimaryBg,
|
|
|
|
|
|
borderRadius: `${token.value.borderRadiusLG}px`,
|
|
|
|
|
|
boxShadow: token.value.boxShadow,
|
|
|
|
|
|
height: '100%',
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
/** کارت سفید بدون بردر (KPI فرعی و چارتها) */
|
|
|
|
|
|
const secondaryCardStyle = computed(() => ({
|
|
|
|
|
|
background: token.value.colorBgContainer,
|
|
|
|
|
|
borderRadius: `${token.value.borderRadiusLG}px`,
|
|
|
|
|
|
boxShadow: token.value.boxShadow,
|
|
|
|
|
|
height: '100%',
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
/** Soft teal family — primary + muted companions (not loud rainbow) */
|
|
|
|
|
|
const chartColors = computed(() => {
|
|
|
|
|
|
const primary = token.value.colorPrimary;
|
|
|
|
|
|
return {
|
|
|
|
|
|
primary,
|
|
|
|
|
|
primarySoft: token.value.colorPrimaryBg,
|
|
|
|
|
|
muted: token.value.colorTextQuaternary || '#a7f3d0',
|
|
|
|
|
|
ink: token.value.colorTextSecondary,
|
|
|
|
|
|
axis: token.value.colorBorderSecondary,
|
|
|
|
|
|
tooltipBg: token.value.colorBgElevated,
|
|
|
|
|
|
font: token.value.fontFamily,
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const flowChartOption = computed<ChartOption>(() => {
|
|
|
|
|
|
if (!data.value) return {};
|
|
|
|
|
|
const c = chartColors.value;
|
|
|
|
|
|
const categories = ['درآمد تعهدی', 'دریافت واقعی', 'هزینه تعهدی', 'پرداخت واقعی'];
|
|
|
|
|
|
const values = [
|
|
|
|
|
|
num(data.value.bookedIncome),
|
|
|
|
|
|
num(data.value.actualReceipts),
|
|
|
|
|
|
num(data.value.bookedExpense),
|
|
|
|
|
|
num(data.value.actualPayments),
|
|
|
|
|
|
];
|
|
|
|
|
|
return {
|
|
|
|
|
|
color: [c.primary, '#34d399', '#6ee7b7', '#a7f3d0'],
|
|
|
|
|
|
textStyle: { fontFamily: c.font, color: c.ink },
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'axis',
|
|
|
|
|
|
backgroundColor: c.tooltipBg,
|
|
|
|
|
|
borderColor: c.axis,
|
|
|
|
|
|
textStyle: { fontFamily: c.font, color: c.ink },
|
|
|
|
|
|
valueFormatter: (v) => toman(Number(v)),
|
|
|
|
|
|
},
|
|
|
|
|
|
grid: { left: 8, right: 12, top: 24, bottom: 8, containLabel: true },
|
|
|
|
|
|
xAxis: {
|
|
|
|
|
|
type: 'category',
|
|
|
|
|
|
data: categories,
|
|
|
|
|
|
axisLine: { lineStyle: { color: c.axis } },
|
|
|
|
|
|
axisTick: { show: false },
|
|
|
|
|
|
axisLabel: {
|
|
|
|
|
|
fontFamily: c.font,
|
|
|
|
|
|
color: c.ink,
|
|
|
|
|
|
interval: 0,
|
|
|
|
|
|
rotate: 0,
|
|
|
|
|
|
fontSize: 11,
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
yAxis: {
|
|
|
|
|
|
type: 'value',
|
|
|
|
|
|
splitLine: { lineStyle: { color: c.axis, type: 'dashed' } },
|
|
|
|
|
|
axisLabel: {
|
|
|
|
|
|
fontFamily: c.font,
|
|
|
|
|
|
color: c.ink,
|
|
|
|
|
|
formatter: (v: number) =>
|
|
|
|
|
|
new Intl.NumberFormat('fa-IR', { notation: 'compact' }).format(v),
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'bar',
|
|
|
|
|
|
data: values.map((value, index) => ({
|
|
|
|
|
|
value,
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
color: [c.primary, '#34d399', '#6ee7b7', '#a7f3d0'][index],
|
|
|
|
|
|
borderRadius: [8, 8, 0, 0],
|
|
|
|
|
|
},
|
|
|
|
|
|
})),
|
|
|
|
|
|
barMaxWidth: 36,
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const mixChartOption = computed<ChartOption>(() => {
|
|
|
|
|
|
if (!data.value) return {};
|
|
|
|
|
|
const c = chartColors.value;
|
|
|
|
|
|
const slices = [
|
|
|
|
|
|
{ name: 'موجودی حسابها', value: Math.max(0, num(data.value.totalBalance)) },
|
|
|
|
|
|
{ name: 'مطالبات', value: Math.max(0, num(data.value.receivables)) },
|
|
|
|
|
|
{ name: 'تعهدات', value: Math.max(0, num(data.value.payables)) },
|
|
|
|
|
|
];
|
|
|
|
|
|
const hasValue = slices.some((s) => s.value > 0);
|
|
|
|
|
|
return {
|
|
|
|
|
|
color: [c.primary, '#34d399', '#a7f3d0'],
|
|
|
|
|
|
textStyle: { fontFamily: c.font, color: c.ink },
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'item',
|
|
|
|
|
|
backgroundColor: c.tooltipBg,
|
|
|
|
|
|
borderColor: c.axis,
|
|
|
|
|
|
textStyle: { fontFamily: c.font, color: c.ink },
|
|
|
|
|
|
formatter: (params: unknown) => {
|
|
|
|
|
|
const p = params as { name?: string; value?: number; percent?: number };
|
|
|
|
|
|
return `${p.name}<br/>${toman(p.value)} (${p.percent ?? 0}٪)`;
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
legend: {
|
|
|
|
|
|
bottom: 0,
|
|
|
|
|
|
icon: 'circle',
|
|
|
|
|
|
itemWidth: 8,
|
|
|
|
|
|
itemHeight: 8,
|
|
|
|
|
|
textStyle: { fontFamily: c.font, color: c.ink, fontSize: 12 },
|
|
|
|
|
|
},
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'pie',
|
|
|
|
|
|
radius: ['52%', '72%'],
|
|
|
|
|
|
center: ['50%', '46%'],
|
|
|
|
|
|
avoidLabelOverlap: true,
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
borderRadius: 8,
|
|
|
|
|
|
borderColor: token.value.colorBgContainer,
|
|
|
|
|
|
borderWidth: 3,
|
|
|
|
|
|
},
|
|
|
|
|
|
label: { show: false },
|
|
|
|
|
|
data: hasValue
|
|
|
|
|
|
? slices
|
|
|
|
|
|
: [{ name: 'بدون داده', value: 1, itemStyle: { color: c.primarySoft } }],
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
2026-10-18 18:49:49 +03:30
|
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
2026-11-03 16:51:51 +03:30
|
|
|
|
loading.value = true;
|
|
|
|
|
|
usingMock.value = false;
|
2026-10-18 18:49:49 +03:30
|
|
|
|
try {
|
|
|
|
|
|
const res = await api.get<TreasuryDashboardDto>('/treasury/dashboard');
|
2026-11-03 16:51:51 +03:30
|
|
|
|
if (isTreasuryDashboardEmpty(res.data)) {
|
|
|
|
|
|
data.value = TREASURY_DASHBOARD_MOCK;
|
|
|
|
|
|
usingMock.value = true;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
data.value = res.data;
|
|
|
|
|
|
}
|
2026-10-18 18:49:49 +03:30
|
|
|
|
} catch {
|
2026-11-03 16:51:51 +03:30
|
|
|
|
data.value = TREASURY_DASHBOARD_MOCK;
|
|
|
|
|
|
usingMock.value = true;
|
|
|
|
|
|
error.value = '';
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false;
|
2026-10-18 18:49:49 +03:30
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
2026-10-18 21:06:18 +03:30
|
|
|
|
<div>
|
2026-11-03 16:51:51 +03:30
|
|
|
|
<a-flex vertical :gap="token.margin">
|
2026-10-18 21:06:18 +03:30
|
|
|
|
<div>
|
|
|
|
|
|
<a-typography-title :level="3" :style="{ margin: 0 }">مدیریت مالی</a-typography-title>
|
|
|
|
|
|
<a-typography-text type="secondary">
|
|
|
|
|
|
دفتر داخلی درآمد، هزینه و موجودی حساب. این بخش جایگزین فاکتور فروش یا حسابداری رسمی نیست.
|
|
|
|
|
|
</a-typography-text>
|
|
|
|
|
|
</div>
|
2026-10-18 18:49:49 +03:30
|
|
|
|
|
2026-10-18 21:06:18 +03:30
|
|
|
|
<TreasuryNav />
|
|
|
|
|
|
|
|
|
|
|
|
<a-alert v-if="error" type="error" show-icon :message="error" />
|
2026-11-03 16:51:51 +03:30
|
|
|
|
<a-alert
|
|
|
|
|
|
v-if="usingMock"
|
|
|
|
|
|
type="info"
|
|
|
|
|
|
show-icon
|
|
|
|
|
|
message="در حال نمایش دادهٔ نمونه (ماک) برای پیشنمایش داشبورد هستید."
|
|
|
|
|
|
description="وقتی سند و حساب واقعی ثبت شود، همین صفحه دادهٔ واقعی API را نشان میدهد."
|
|
|
|
|
|
/>
|
2026-10-18 21:06:18 +03:30
|
|
|
|
|
2026-11-03 16:51:51 +03:30
|
|
|
|
<a-spin :spinning="loading">
|
|
|
|
|
|
<template v-if="data">
|
|
|
|
|
|
<a-row :gutter="[token.marginSM, token.marginSM]">
|
|
|
|
|
|
<a-col
|
|
|
|
|
|
v-for="card in heroCards"
|
|
|
|
|
|
:key="card.key"
|
|
|
|
|
|
:xs="24"
|
|
|
|
|
|
:sm="12"
|
|
|
|
|
|
:lg="8"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-card
|
|
|
|
|
|
:bordered="false"
|
|
|
|
|
|
:body-style="{ padding: `${token.padding}px` }"
|
|
|
|
|
|
:style="softCardStyle"
|
2026-10-18 21:06:18 +03:30
|
|
|
|
>
|
2026-11-03 16:51:51 +03:30
|
|
|
|
<a-flex align="center" justify="space-between" :gap="token.marginSM">
|
|
|
|
|
|
<div :style="{ minWidth: 0, flex: 1 }">
|
|
|
|
|
|
<a-typography-text type="secondary">{{ card.label }}</a-typography-text>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<a-typography-text
|
|
|
|
|
|
strong
|
|
|
|
|
|
:style="{
|
|
|
|
|
|
fontSize: '22px',
|
|
|
|
|
|
lineHeight: 1.3,
|
|
|
|
|
|
color: token.colorPrimary,
|
|
|
|
|
|
}"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ toman(data[card.key]) }}
|
|
|
|
|
|
</a-typography-text>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<a-typography-text type="secondary" :style="{ fontSize: '12px' }">
|
|
|
|
|
|
{{ card.hint }}
|
|
|
|
|
|
</a-typography-text>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<a-avatar
|
|
|
|
|
|
:size="48"
|
|
|
|
|
|
:style="{
|
|
|
|
|
|
background: token.colorPrimary,
|
|
|
|
|
|
color: token.colorTextLightSolid,
|
|
|
|
|
|
flexShrink: 0,
|
|
|
|
|
|
}"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #icon><component :is="card.icon" /></template>
|
|
|
|
|
|
</a-avatar>
|
|
|
|
|
|
</a-flex>
|
|
|
|
|
|
</a-card>
|
2026-10-18 21:06:18 +03:30
|
|
|
|
</a-col>
|
|
|
|
|
|
</a-row>
|
|
|
|
|
|
|
2026-11-03 16:51:51 +03:30
|
|
|
|
<a-row :gutter="[token.marginSM, token.marginSM]" :style="{ marginTop: `${token.margin}px` }">
|
|
|
|
|
|
<a-col
|
|
|
|
|
|
v-for="card in secondaryCards"
|
|
|
|
|
|
:key="card.key"
|
|
|
|
|
|
:xs="24"
|
|
|
|
|
|
:sm="12"
|
|
|
|
|
|
:md="8"
|
|
|
|
|
|
:lg="8"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-card
|
|
|
|
|
|
:bordered="false"
|
|
|
|
|
|
:body-style="{ padding: `${token.paddingSM}px` }"
|
|
|
|
|
|
:style="secondaryCardStyle"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-flex align="center" justify="space-between" :gap="8">
|
|
|
|
|
|
<div :style="{ minWidth: 0, flex: 1 }">
|
|
|
|
|
|
<a-typography-text type="secondary" :style="{ fontSize: '12px' }">
|
|
|
|
|
|
{{ card.label }}
|
|
|
|
|
|
</a-typography-text>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<a-typography-text
|
|
|
|
|
|
strong
|
|
|
|
|
|
:style="{ fontSize: '15px', color: token.colorPrimary }"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ toman(data[card.key]) }}
|
|
|
|
|
|
</a-typography-text>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<a-typography-text type="secondary" :style="{ fontSize: '11px' }">
|
|
|
|
|
|
{{ card.hint }}
|
|
|
|
|
|
</a-typography-text>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<a-avatar
|
|
|
|
|
|
:size="36"
|
|
|
|
|
|
:style="{
|
|
|
|
|
|
background: token.colorPrimaryBg,
|
|
|
|
|
|
color: token.colorPrimary,
|
|
|
|
|
|
flexShrink: 0,
|
|
|
|
|
|
}"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #icon><component :is="card.icon" /></template>
|
|
|
|
|
|
</a-avatar>
|
|
|
|
|
|
</a-flex>
|
|
|
|
|
|
</a-card>
|
|
|
|
|
|
</a-col>
|
|
|
|
|
|
</a-row>
|
|
|
|
|
|
|
|
|
|
|
|
<a-row :gutter="[token.marginSM, token.marginSM]" :style="{ marginTop: `${token.margin}px` }">
|
|
|
|
|
|
<a-col :xs="24" :lg="14">
|
|
|
|
|
|
<a-card
|
|
|
|
|
|
:bordered="false"
|
|
|
|
|
|
:style="secondaryCardStyle"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-typography-text strong>جریان تعهدی و نقدی</a-typography-text>
|
|
|
|
|
|
<a-typography-text
|
|
|
|
|
|
type="secondary"
|
|
|
|
|
|
:style="{ display: 'block', marginBottom: `${token.marginSM}px`, fontSize: '12px' }"
|
|
|
|
|
|
>
|
|
|
|
|
|
مقایسه درآمد/هزینه ثبتشده با دریافت و پرداخت واقعی
|
|
|
|
|
|
</a-typography-text>
|
|
|
|
|
|
<VChart
|
|
|
|
|
|
:option="flowChartOption"
|
|
|
|
|
|
autoresize
|
|
|
|
|
|
:style="{ height: '260px', width: '100%' }"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-card>
|
|
|
|
|
|
</a-col>
|
|
|
|
|
|
<a-col :xs="24" :lg="10">
|
|
|
|
|
|
<a-card
|
|
|
|
|
|
:bordered="false"
|
|
|
|
|
|
:style="secondaryCardStyle"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-typography-text strong>ترکیب وضعیت مالی</a-typography-text>
|
|
|
|
|
|
<a-typography-text
|
|
|
|
|
|
type="secondary"
|
|
|
|
|
|
:style="{ display: 'block', marginBottom: `${token.marginSM}px`, fontSize: '12px' }"
|
|
|
|
|
|
>
|
|
|
|
|
|
سهم موجودی، مطالبات و تعهدات
|
|
|
|
|
|
</a-typography-text>
|
|
|
|
|
|
<VChart
|
|
|
|
|
|
:option="mixChartOption"
|
|
|
|
|
|
autoresize
|
|
|
|
|
|
:style="{ height: '260px', width: '100%' }"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-card>
|
|
|
|
|
|
</a-col>
|
|
|
|
|
|
</a-row>
|
|
|
|
|
|
|
|
|
|
|
|
<a-row :gutter="[token.marginSM, token.marginSM]" :style="{ marginTop: `${token.margin}px` }">
|
|
|
|
|
|
<a-col :xs="24" :lg="8">
|
|
|
|
|
|
<a-card
|
|
|
|
|
|
:bordered="false"
|
|
|
|
|
|
title="اقساط نزدیک یا معوق"
|
|
|
|
|
|
:style="secondaryCardStyle"
|
|
|
|
|
|
:body-style="{ paddingBlock: `${token.paddingSM}px` }"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-empty
|
|
|
|
|
|
v-if="!data.upcomingInstallments.length"
|
|
|
|
|
|
description="قسط بازی نیست."
|
|
|
|
|
|
:image-style="{ height: '48px' }"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<a-flex v-else vertical :gap="token.marginXS">
|
|
|
|
|
|
<a-flex
|
|
|
|
|
|
v-for="item in data.upcomingInstallments"
|
|
|
|
|
|
:key="item.id"
|
|
|
|
|
|
justify="space-between"
|
|
|
|
|
|
align="flex-start"
|
|
|
|
|
|
:gap="8"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div :style="{ minWidth: 0 }">
|
|
|
|
|
|
<a-typography-text>قسط {{ item.position }}</a-typography-text>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<a-typography-text type="secondary" :style="{ fontSize: '12px' }">
|
|
|
|
|
|
{{ item.status === 'overdue' ? 'معوق' : item.status === 'partial' ? 'جزئی' : 'باز' }}
|
|
|
|
|
|
· مانده {{ toman(item.remaining) }}
|
|
|
|
|
|
</a-typography-text>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<a-typography-text strong :style="{ color: token.colorPrimary, whiteSpace: 'nowrap' }">
|
|
|
|
|
|
{{ toman(item.amount) }}
|
|
|
|
|
|
</a-typography-text>
|
|
|
|
|
|
</a-flex>
|
|
|
|
|
|
</a-flex>
|
|
|
|
|
|
</a-card>
|
|
|
|
|
|
</a-col>
|
|
|
|
|
|
|
|
|
|
|
|
<a-col :xs="24" :lg="8">
|
|
|
|
|
|
<a-card
|
|
|
|
|
|
:bordered="false"
|
|
|
|
|
|
title="پرداختهای آینده"
|
|
|
|
|
|
:style="secondaryCardStyle"
|
|
|
|
|
|
:body-style="{ paddingBlock: `${token.paddingSM}px` }"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-empty
|
|
|
|
|
|
v-if="!data.upcomingPayables.length"
|
|
|
|
|
|
description="تعهد پرداخت بازی نیست."
|
|
|
|
|
|
:image-style="{ height: '48px' }"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<a-flex v-else vertical :gap="token.marginXS">
|
|
|
|
|
|
<a-flex
|
|
|
|
|
|
v-for="item in data.upcomingPayables"
|
|
|
|
|
|
:key="item.id"
|
|
|
|
|
|
justify="space-between"
|
|
|
|
|
|
align="flex-start"
|
|
|
|
|
|
:gap="8"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div :style="{ minWidth: 0 }">
|
|
|
|
|
|
<a-typography-text>{{ item.title }}</a-typography-text>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<a-typography-text type="secondary" :style="{ fontSize: '12px' }">
|
|
|
|
|
|
مانده {{ toman(item.remaining) }}
|
|
|
|
|
|
</a-typography-text>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<a-typography-text strong :style="{ color: token.colorPrimary, whiteSpace: 'nowrap' }">
|
|
|
|
|
|
{{ toman(item.amount) }}
|
|
|
|
|
|
</a-typography-text>
|
|
|
|
|
|
</a-flex>
|
|
|
|
|
|
</a-flex>
|
|
|
|
|
|
</a-card>
|
|
|
|
|
|
</a-col>
|
|
|
|
|
|
|
|
|
|
|
|
<a-col :xs="24" :lg="8">
|
|
|
|
|
|
<a-card
|
|
|
|
|
|
:bordered="false"
|
|
|
|
|
|
title="آخرین اسناد"
|
|
|
|
|
|
:style="secondaryCardStyle"
|
|
|
|
|
|
:body-style="{ paddingBlock: `${token.paddingSM}px` }"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-empty
|
|
|
|
|
|
v-if="!data.recent.length"
|
|
|
|
|
|
description="هنوز سندی ثبت نشده است."
|
|
|
|
|
|
:image-style="{ height: '48px' }"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<a-flex v-else vertical :gap="token.marginXS">
|
|
|
|
|
|
<a-flex
|
|
|
|
|
|
v-for="item in data.recent"
|
|
|
|
|
|
:key="item.id"
|
|
|
|
|
|
justify="space-between"
|
|
|
|
|
|
align="flex-start"
|
|
|
|
|
|
:gap="8"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div :style="{ minWidth: 0 }">
|
|
|
|
|
|
<a-typography-text>{{ item.title }}</a-typography-text>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<a-typography-text type="secondary" :style="{ fontSize: '12px' }">
|
|
|
|
|
|
{{ item.kind === 'income' ? 'درآمد' : 'هزینه' }} · {{ item.status }}
|
|
|
|
|
|
</a-typography-text>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<a-typography-text strong :style="{ color: token.colorPrimary, whiteSpace: 'nowrap' }">
|
|
|
|
|
|
{{ toman(item.amount) }}
|
|
|
|
|
|
</a-typography-text>
|
|
|
|
|
|
</a-flex>
|
|
|
|
|
|
</a-flex>
|
|
|
|
|
|
</a-card>
|
|
|
|
|
|
</a-col>
|
|
|
|
|
|
</a-row>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</a-spin>
|
2026-10-18 21:06:18 +03:30
|
|
|
|
</a-flex>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|