Files
kt-financial-system/apps/web-finance/src/views/finance/budget/components/budget-setting.vue
你的用户名 675fe0a1a8 feat: 增强财务管理系统功能与分析能力
主要更新:
- 🎯 新增综合分析仪表板,包含关键指标卡片、预算对比、智能洞察等组件
- 📊 增强数据可视化能力,新增标签云分析、时间维度分析等图表
- 📱 优化移动端响应式设计,改进触控交互体验
- 🔧 新增多个API模块(base、budget、tag),完善数据管理
- 🗂️ 重构路由结构,新增贷款、快速添加、设置、统计等独立模块
- 🔄 优化数据导入导出功能,增强数据迁移能力
- 🐛 修复多个已知问题,提升系统稳定性

技术改进:
- 使用IndexedDB提升本地存储性能
- 实现模拟API服务,支持离线开发
- 增加自动化测试脚本,确保功能稳定
- 优化打包配置,提升构建效率

文件变更:
- 新增42个文件
- 修改55个文件
- 包含测试脚本、配置文件、组件和API模块

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-24 16:41:58 +08:00

256 lines
7.0 KiB
Vue

<script setup lang="ts">
import type { FormInstance, Rule } from 'ant-design-vue';
import type { Budget } from '#/types/finance';
import { computed, ref, watch } from 'vue';
import {
Col,
Form,
FormItem,
InputNumber,
message,
Modal,
Row,
Select,
SelectOption,
} from 'ant-design-vue';
import dayjs from 'dayjs';
import { useBudgetStore } from '#/store/modules/budget';
import { useCategoryStore } from '#/store/modules/category';
interface Props {
visible: boolean;
budget?: Budget | null;
}
const props = withDefaults(defineProps<Props>(), {
visible: false,
budget: null,
});
const emit = defineEmits<{
success: [];
'update:visible': [value: boolean];
}>();
const budgetStore = useBudgetStore();
const categoryStore = useCategoryStore();
const formRef = ref<FormInstance>();
const formData = ref({
categoryId: '',
amount: 0,
currency: 'CNY',
period: 'monthly' as 'monthly' | 'yearly',
year: dayjs().year(),
month: dayjs().month() + 1,
});
const rules: Record<string, Rule[]> = {
categoryId: [{ required: true, message: '请选择分类' }],
amount: [
{ required: true, message: '请输入预算金额' },
{ type: 'number', min: 0.01, message: '预算金额必须大于0' },
],
currency: [{ required: true, message: '请选择货币' }],
period: [{ required: true, message: '请选择预算周期' }],
year: [{ required: true, message: '请选择年份' }],
month: [{ required: true, message: '请选择月份' }],
};
const title = computed(() => (props.budget ? '编辑预算' : '设置预算'));
const expenseCategories = computed(() =>
categoryStore.categories.filter((c) => c.type === 'expense'),
);
const yearOptions = computed(() => {
const currentYear = dayjs().year();
return Array.from({ length: 5 }, (_, i) => currentYear - 2 + i);
});
const visible = computed({
get: () => props.visible,
set: (val) => emit('update:visible', val),
});
const isCategoryBudgetExists = (categoryId: string) => {
if (props.budget && props.budget.categoryId === categoryId) {
return false;
}
return budgetStore.isBudgetExists(
categoryId,
formData.value.year,
formData.value.period,
formData.value.period === 'monthly' ? formData.value.month : undefined,
);
};
const handlePeriodChange = () => {
formData.value.month =
formData.value.period === 'yearly'
? (undefined as any)
: dayjs().month() + 1;
};
const handleSubmit = async () => {
try {
await formRef.value?.validate();
const data = {
...formData.value,
month:
formData.value.period === 'monthly' ? formData.value.month : undefined,
};
if (props.budget) {
await budgetStore.updateBudget(props.budget.id, data);
message.success('预算更新成功');
} else {
await budgetStore.createBudget(data);
message.success('预算设置成功');
}
emit('success');
visible.value = false;
} catch (error) {
if (error !== 'Validation failed') {
message.error(props.budget ? '更新预算失败' : '设置预算失败');
}
}
};
const handleCancel = () => {
formRef.value?.resetFields();
visible.value = false;
};
watch(
() => props.visible,
(newVal) => {
if (newVal) {
formData.value = props.budget
? {
categoryId: props.budget.categoryId,
amount: props.budget.amount,
currency: props.budget.currency,
period: props.budget.period,
year: props.budget.year,
month: props.budget.month || dayjs().month() + 1,
}
: {
categoryId: '',
amount: 0,
currency: 'CNY',
period: 'monthly',
year: dayjs().year(),
month: dayjs().month() + 1,
};
}
},
);
</script>
<template>
<div class="budget-setting">
<Modal
v-model:open="visible"
:title="title"
width="500px"
@ok="handleSubmit"
@cancel="handleCancel"
>
<Form ref="formRef" :model="formData" :rules="rules" layout="vertical">
<FormItem label="分类" name="categoryId">
<Select
v-model:value="formData.categoryId"
placeholder="选择分类"
:disabled="!!budget"
>
<SelectOption
v-for="category in expenseCategories"
:key="category.id"
:value="category.id"
:disabled="isCategoryBudgetExists(category.id)"
>
{{ category.icon }} {{ category.name }}
<span
v-if="isCategoryBudgetExists(category.id)"
style="color: #999"
>
(已设置预算)
</span>
</SelectOption>
</Select>
</FormItem>
<Row :gutter="16">
<Col :span="12">
<FormItem label="预算周期" name="period">
<Select
v-model:value="formData.period"
@change="handlePeriodChange"
>
<SelectOption value="monthly">月度预算</SelectOption>
<SelectOption value="yearly">年度预算</SelectOption>
</Select>
</FormItem>
</Col>
<Col :span="12">
<FormItem label="预算金额" name="amount">
<InputNumber
v-model:value="formData.amount"
:min="0"
:precision="2"
placeholder="输入预算金额"
style="width: 100%"
:formatter="
(value) => `¥ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
"
:parser="(value) => value.replace(/\¥\s?|(,*)/g, '')"
/>
</FormItem>
</Col>
</Row>
<Row :gutter="16">
<Col :span="12">
<FormItem label="年份" name="year">
<Select v-model:value="formData.year">
<SelectOption
v-for="year in yearOptions"
:key="year"
:value="year"
>
{{ year }}
</SelectOption>
</Select>
</FormItem>
</Col>
<Col :span="12" v-if="formData.period === 'monthly'">
<FormItem label="月份" name="month">
<Select v-model:value="formData.month">
<SelectOption v-for="month in 12" :key="month" :value="month">
{{ month }}
</SelectOption>
</Select>
</FormItem>
</Col>
</Row>
<FormItem label="货币" name="currency">
<Select v-model:value="formData.currency">
<SelectOption value="USD">USD ($)</SelectOption>
<SelectOption value="CNY">CNY (¥)</SelectOption>
<SelectOption value="THB">THB (฿)</SelectOption>
<SelectOption value="MMK">MMK (K)</SelectOption>
</Select>
</FormItem>
</Form>
</Modal>
</div>
</template>