feat(web): adopt vuesax icons and shared skeleton loading
Replace the inline SVG icon set with the vuesax linear pack, add skeleton primitives for loading states, and keep page scroll inside the app shell. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -8,7 +8,6 @@ import {
|
||||
Empty as AEmpty,
|
||||
Flex as AFlex,
|
||||
Row as ARow,
|
||||
Spin as ASpin,
|
||||
Typography,
|
||||
theme,
|
||||
} from 'ant-design-vue';
|
||||
@@ -22,7 +21,13 @@ import {
|
||||
FallOutlined,
|
||||
RiseOutlined,
|
||||
WalletOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
} from '@/icons/ant-bridge';
|
||||
import {
|
||||
CrmSkeleton,
|
||||
CrmSkeletonCard,
|
||||
CrmSkeletonKpi,
|
||||
CrmSkeletonList,
|
||||
} from '@/components/skeleton';
|
||||
import { use } from 'echarts/core';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { BarChart, PieChart } from 'echarts/charts';
|
||||
@@ -314,17 +319,18 @@ onMounted(async () => {
|
||||
description="وقتی سند و حساب واقعی ثبت شود، همین صفحه دادهٔ واقعی API را نشان میدهد."
|
||||
/>
|
||||
|
||||
<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"
|
||||
>
|
||||
<CrmSkeleton :loading="loading">
|
||||
<a-row :gutter="[token.marginSM, token.marginSM]">
|
||||
<a-col
|
||||
v-for="card in heroCards"
|
||||
:key="card.key"
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:lg="8"
|
||||
>
|
||||
<CrmSkeletonKpi>
|
||||
<a-card
|
||||
v-if="data"
|
||||
:bordered="false"
|
||||
:body-style="{ padding: `${token.padding}px` }"
|
||||
:style="softCardStyle"
|
||||
@@ -360,19 +366,22 @@ onMounted(async () => {
|
||||
</a-avatar>
|
||||
</a-flex>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</CrmSkeletonKpi>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<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-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"
|
||||
>
|
||||
<CrmSkeletonKpi>
|
||||
<a-card
|
||||
v-if="data"
|
||||
:bordered="false"
|
||||
:body-style="{ padding: `${token.paddingSM}px` }"
|
||||
:style="secondaryCardStyle"
|
||||
@@ -406,162 +415,189 @@ onMounted(async () => {
|
||||
</a-avatar>
|
||||
</a-flex>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</CrmSkeletonKpi>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<template v-if="data">
|
||||
<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' }"
|
||||
<CrmSkeletonCard :rows="6" :min-height="320">
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:style="secondaryCardStyle"
|
||||
>
|
||||
مقایسه درآمد/هزینه ثبتشده با دریافت و پرداخت واقعی
|
||||
</a-typography-text>
|
||||
<VChart
|
||||
:option="flowChartOption"
|
||||
autoresize
|
||||
:style="{ height: '260px', width: '100%' }"
|
||||
/>
|
||||
</a-card>
|
||||
<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>
|
||||
</CrmSkeletonCard>
|
||||
</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' }"
|
||||
<CrmSkeletonCard :rows="6" :min-height="320">
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:style="secondaryCardStyle"
|
||||
>
|
||||
سهم موجودی، مطالبات و تعهدات
|
||||
</a-typography-text>
|
||||
<VChart
|
||||
:option="mixChartOption"
|
||||
autoresize
|
||||
:style="{ height: '260px', width: '100%' }"
|
||||
/>
|
||||
</a-card>
|
||||
<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>
|
||||
</CrmSkeletonCard>
|
||||
</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>
|
||||
<CrmSkeletonList :rows="4" :avatar="false">
|
||||
<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>
|
||||
</div>
|
||||
<a-typography-text strong :style="{ color: token.colorPrimary, whiteSpace: 'nowrap' }">
|
||||
{{ toman(item.amount) }}
|
||||
</a-typography-text>
|
||||
<a-typography-text strong :style="{ color: token.colorPrimary, whiteSpace: 'nowrap' }">
|
||||
{{ toman(item.amount) }}
|
||||
</a-typography-text>
|
||||
</a-flex>
|
||||
</a-flex>
|
||||
</a-flex>
|
||||
</a-card>
|
||||
</a-card>
|
||||
</CrmSkeletonList>
|
||||
</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>
|
||||
<CrmSkeletonList :rows="4" :avatar="false">
|
||||
<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>
|
||||
</div>
|
||||
<a-typography-text strong :style="{ color: token.colorPrimary, whiteSpace: 'nowrap' }">
|
||||
{{ toman(item.amount) }}
|
||||
</a-typography-text>
|
||||
<a-typography-text strong :style="{ color: token.colorPrimary, whiteSpace: 'nowrap' }">
|
||||
{{ toman(item.amount) }}
|
||||
</a-typography-text>
|
||||
</a-flex>
|
||||
</a-flex>
|
||||
</a-flex>
|
||||
</a-card>
|
||||
</a-card>
|
||||
</CrmSkeletonList>
|
||||
</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>
|
||||
<CrmSkeletonList :rows="4" :avatar="false">
|
||||
<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>
|
||||
</div>
|
||||
<a-typography-text strong :style="{ color: token.colorPrimary, whiteSpace: 'nowrap' }">
|
||||
{{ toman(item.amount) }}
|
||||
</a-typography-text>
|
||||
<a-typography-text strong :style="{ color: token.colorPrimary, whiteSpace: 'nowrap' }">
|
||||
{{ toman(item.amount) }}
|
||||
</a-typography-text>
|
||||
</a-flex>
|
||||
</a-flex>
|
||||
</a-flex>
|
||||
</a-card>
|
||||
</a-card>
|
||||
</CrmSkeletonList>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</a-spin>
|
||||
<template v-else-if="loading">
|
||||
<a-row :gutter="[token.marginSM, token.marginSM]" :style="{ marginTop: `${token.margin}px` }">
|
||||
<a-col :xs="24" :lg="14">
|
||||
<CrmSkeletonCard :rows="6" :min-height="320" />
|
||||
</a-col>
|
||||
<a-col :xs="24" :lg="10">
|
||||
<CrmSkeletonCard :rows="6" :min-height="320" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="[token.marginSM, token.marginSM]" :style="{ marginTop: `${token.margin}px` }">
|
||||
<a-col v-for="i in 3" :key="i" :xs="24" :lg="8">
|
||||
<CrmSkeletonList :rows="4" :avatar="false" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</CrmSkeleton>
|
||||
</a-flex>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
PlusOutlined,
|
||||
TagOutlined,
|
||||
UserOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
} from '@/icons/ant-bridge';
|
||||
import { api } from '@/lib/api';
|
||||
import { formatLocalDateInput } from '@/lib/jalali';
|
||||
import type {
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
Typography,
|
||||
} from 'ant-design-vue';
|
||||
import type { TableColumnsType } from 'ant-design-vue';
|
||||
import { DownloadOutlined } from '@ant-design/icons-vue';
|
||||
import { DownloadOutlined } from '@/icons/ant-bridge';
|
||||
import { api } from '@/lib/api';
|
||||
import { toman } from './treasury-format';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user