feat(web): migrate finance treasury pages to Ant Design Vue

Replace legacy Ui* chrome on treasury routes with Ant layout, and polish mobile incomes stats colors plus nav visibility.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-18 21:06:18 +03:30
co-authored by Cursor
parent b25b5b125a
commit 8fdd5147a5
10 changed files with 1985 additions and 472 deletions
@@ -1,12 +1,32 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import axios from 'axios';
import {
Alert as AAlert,
Button as AButton,
Card as ACard,
Col as ACol,
Divider as ADivider,
Empty as AEmpty,
Flex as AFlex,
Form as AForm,
FormItem as AFormItem,
Input as AInput,
List as AList,
Row as ARow,
Select as ASelect,
Typography,
} from 'ant-design-vue';
import { api } from '@/lib/api';
import { UiButton, UiCard, UiField, UiInput, UiPageHeader } from '@/components/ui';
import TreasuryNav from '@/components/treasury/TreasuryNav.vue';
import { toman } from './treasury-format';
import { formatJalaliNumeric } from '@/lib/jalali';
const { Title: ATypographyTitle, Text: ATypographyText } = Typography;
const AListItem = AList.Item;
const AListItemMeta = AList.Item.Meta;
const ASelectOption = ASelect.Option;
type Row = { id: string; title: string; kind: string; amount: string; interval: string; nextDue: string; isActive: boolean };
const rows = ref<Row[]>([]);
const error = ref('');
@@ -37,48 +57,80 @@ onMounted(() => {
</script>
<template>
<section class="page">
<UiPageHeader title="درآمد و هزینه تکرارشونده" description="تا تأیید دریافت یا پرداخت، موجودی حساب عوض نمی‌شود." />
<TreasuryNav />
<p v-if="error" class="err">{{ error }}</p>
<p v-if="ok" class="ok">{{ ok }}</p>
<UiCard title="الگوی جدید">
<div class="grid">
<UiField label="عنوان"><UiInput v-model="form.title" /></UiField>
<UiField label="نوع">
<select v-model="form.kind" class="select">
<option value="expense">هزینه</option>
<option value="income">درآمد</option>
</select>
</UiField>
<UiField label="مبلغ"><UiInput v-model="form.amount" /></UiField>
<UiField label="دوره">
<select v-model="form.interval" class="select">
<option value="monthly">ماهانه</option>
<option value="weekly">هفتگی</option>
<option value="yearly">سالانه</option>
</select>
</UiField>
<UiField label="شروع"><UiInput v-model="form.startDate" type="date" /></UiField>
</div>
<UiButton @click="createItem">ثبت الگو</UiButton>
</UiCard>
<article v-for="row in rows" :key="row.id" class="row">
<div>
<a-flex vertical :gap="16">
<div>
<strong>{{ row.title }}</strong>
<small>{{ row.kind === 'income' ? 'درآمد' : 'هزینه' }} · {{ row.interval }} · بعدی {{ formatJalaliNumeric(new Date(row.nextDue)) }}</small>
<a-typography-title :level="3" :style="{ margin: 0 }">درآمد و هزینه تکرارشونده</a-typography-title>
<a-typography-text type="secondary">
تا تأیید دریافت یا پرداخت، موجودی حساب عوض نمی‌شود.
</a-typography-text>
</div>
<b>{{ toman(row.amount) }}</b>
</article>
</section>
</template>
<style scoped>
.page { display: grid; gap: 1rem; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 0.7rem; margin-bottom: 0.8rem; }
.select { width: 100%; min-height: var(--control-h); border: 1px solid #e2e8f0; border-radius: 12px; background: #fff; }
.row { display: flex; justify-content: space-between; background: #fff; border: 1px solid #e7eef2; border-radius: 14px; padding: 0.8rem 1rem; }
.row small { display: block; color: #64748b; }
.err { color: #b91c1c; font-weight: 700; }
.ok { color: #0f766e; font-weight: 700; }
</style>
<TreasuryNav />
<a-alert v-if="error" type="error" show-icon :message="error" />
<a-alert v-if="ok" type="success" show-icon :message="ok" />
<a-card :bordered="false">
<a-divider orientation="right">الگوی جدید</a-divider>
<a-form layout="vertical" @finish="createItem">
<a-row :gutter="[16, 0]">
<a-col :xs="24" :md="12">
<a-form-item label="عنوان" name="title">
<a-input v-model:value="form.title" />
</a-form-item>
</a-col>
<a-col :xs="24" :md="8">
<a-form-item label="نوع" name="kind">
<a-select v-model:value="form.kind" style="width: 100%">
<a-select-option value="expense">هزینه</a-select-option>
<a-select-option value="income">درآمد</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :xs="24" :md="8">
<a-form-item label="مبلغ" name="amount">
<a-input v-model:value="form.amount" />
</a-form-item>
</a-col>
<a-col :xs="24" :md="8">
<a-form-item label="دوره" name="interval">
<a-select v-model:value="form.interval" style="width: 100%">
<a-select-option value="monthly">ماهانه</a-select-option>
<a-select-option value="weekly">هفتگی</a-select-option>
<a-select-option value="yearly">سالانه</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :xs="24" :md="8">
<a-form-item label="شروع" name="startDate">
<a-input v-model:value="form.startDate" type="date" style="width: 100%" />
</a-form-item>
</a-col>
</a-row>
<a-button type="primary" html-type="submit">ثبت الگو</a-button>
</a-form>
<a-divider orientation="right">فهرست الگوها</a-divider>
<a-empty v-if="!rows.length" description="الگویی ثبت نشده است." />
<a-list v-else item-layout="horizontal" :data-source="rows">
<template #renderItem="{ item }">
<a-list-item>
<template #actions>
<a-typography-text strong>{{ toman(item.amount) }}</a-typography-text>
</template>
<a-list-item-meta>
<template #title>{{ item.title }}</template>
<template #description>
{{ item.kind === 'income' ? 'درآمد' : 'هزینه' }}
· {{ item.interval }}
· بعدی {{ formatJalaliNumeric(new Date(item.nextDue)) }}
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
</a-card>
</a-flex>
</div>
</template>