feat: 完善项目架构并增强分析页面功能

- 新增项目文档和 Docker 配置
  - 添加 README.md 和 TODO.md 项目文档
  - 为各服务添加 Dockerfile 和 docker-compose 配置

- 重构后端架构
  - 新增 adapter 层(HTTP/Python 适配器)
  - 新增 repository 层(数据访问抽象)
  - 新增 router 模块统一管理路由
  - 新增账单处理 handler

- 扩展前端 UI 组件库
  - 新增 Calendar、DateRangePicker、Drawer、Popover 等组件
  - 集成 shadcn-svelte 组件库

- 增强分析页面功能
  - 添加时间范围筛选器(支持本月默认值)
  - 修复 DateRangePicker 默认值显示问题
  - 优化数据获取和展示逻辑

- 完善分析器服务
  - 新增 FastAPI 服务接口
  - 改进账单清理器实现
This commit is contained in:
2026-01-10 01:15:52 +08:00
parent 94f8ea12e6
commit 087ae027cc
96 changed files with 4301 additions and 482 deletions

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { uploadBill, type UploadResponse } from '$lib/api';
import { uploadBill, type UploadResponse, type BillType } from '$lib/api';
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Badge } from '$lib/components/ui/badge';
@@ -16,6 +16,7 @@
let isDragOver = $state(false);
let selectedFile: File | null = $state(null);
let selectedType: BillType = $state('alipay');
let isUploading = $state(false);
let uploadResult: UploadResponse | null = $state(null);
let errorMessage = $state('');
@@ -86,6 +87,14 @@
selectedFile = file;
errorMessage = '';
uploadResult = null;
// 根据文件名自动识别账单类型
const fileName = file.name.toLowerCase();
if (fileName.includes('支付宝') || fileName.includes('alipay')) {
selectedType = 'alipay';
} else if (fileName.includes('微信') || fileName.includes('wechat')) {
selectedType = 'wechat';
}
}
function clearFile() {
@@ -101,7 +110,7 @@
errorMessage = '';
try {
const result = await uploadBill(selectedFile);
const result = await uploadBill(selectedFile, selectedType);
if (result.result) {
uploadResult = result;
} else {
@@ -135,7 +144,7 @@
<!-- 统计卡片 -->
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
{#each stats as stat}
<Card.Root>
<Card.Root class="transition-all duration-200 hover:shadow-lg hover:-translate-y-1 cursor-default">
<Card.Header class="flex flex-row items-center justify-between space-y-0 pb-2">
<Card.Title class="text-sm font-medium">{stat.title}</Card.Title>
{#if stat.trend === 'up'}
@@ -226,6 +235,27 @@
</div>
{/if}
<!-- 账单类型选择 -->
<div class="flex items-center gap-3">
<span class="text-sm font-medium">账单类型:</span>
<div class="flex gap-2">
<Button
variant={selectedType === 'alipay' ? 'default' : 'outline'}
size="sm"
onclick={() => selectedType = 'alipay'}
>
支付宝
</Button>
<Button
variant={selectedType === 'wechat' ? 'default' : 'outline'}
size="sm"
onclick={() => selectedType = 'wechat'}
>
微信
</Button>
</div>
</div>
<!-- 上传按钮 -->
<Button
class="w-full"
@@ -256,7 +286,7 @@
<CheckCircle class="h-5 w-5 text-green-600 dark:text-green-400" />
<div>
<p class="font-medium text-green-800 dark:text-green-200">处理成功</p>
<p class="text-sm text-green-600 dark:text-green-400">账单已分析完成</p>
<p class="text-sm text-green-600 dark:text-green-400">{uploadResult.message}</p>
</div>
</div>
@@ -267,6 +297,14 @@
{uploadResult.data?.bill_type === 'alipay' ? '支付宝' : '微信'}
</Badge>
</div>
<div class="flex items-center justify-between">
<span class="text-sm text-muted-foreground">原始记录数</span>
<span class="text-sm font-medium">{uploadResult.data?.raw_count ?? 0}</span>
</div>
<div class="flex items-center justify-between">
<span class="text-sm text-muted-foreground">清洗后记录数</span>
<span class="text-sm font-medium">{uploadResult.data?.cleaned_count ?? 0}</span>
</div>
<div class="flex items-center justify-between">
<span class="text-sm text-muted-foreground">输出文件</span>
<span class="text-sm font-medium">{uploadResult.data?.file_name}</span>
@@ -275,7 +313,7 @@
<div class="flex gap-3 pt-2">
<a
href={`http://localhost:8080${uploadResult.data?.file_url}`}
href={uploadResult.data?.file_url || '#'}
download
class="flex-1"
>