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

@@ -0,0 +1,83 @@
<script lang="ts">
import { CalendarDate, type DateValue } from "@internationalized/date";
import CalendarIcon from "@lucide/svelte/icons/calendar";
import * as Popover from "$lib/components/ui/popover";
import { RangeCalendar } from "$lib/components/ui/range-calendar";
import { Button } from "$lib/components/ui/button";
import { cn } from "$lib/utils";
import type { DateRange } from "bits-ui";
interface Props {
startDate?: string;
endDate?: string;
onchange?: (start: string, end: string) => void;
class?: string;
}
let { startDate = $bindable(), endDate = $bindable(), onchange, class: className }: Props = $props();
// 将 YYYY-MM-DD 字符串转换为 CalendarDate
function parseDate(dateStr: string): DateValue | undefined {
if (!dateStr) return undefined;
const [year, month, day] = dateStr.split('-').map(Number);
return new CalendarDate(year, month, day);
}
// 将 CalendarDate 转换为 YYYY-MM-DD 字符串
function formatDate(date: DateValue | undefined): string {
if (!date) return '';
return `${date.year}-${String(date.month).padStart(2, '0')}-${String(date.day).padStart(2, '0')}`;
}
// 内部日期范围状态,使用 $derived 响应 props 变化
let value: DateRange = $derived({
start: parseDate(startDate),
end: parseDate(endDate)
});
// 格式化显示文本
let displayText = $derived(() => {
if (value.start && value.end) {
return `${formatDate(value.start)} ~ ${formatDate(value.end)}`;
}
if (value.start) {
return `${formatDate(value.start)} ~ `;
}
return "选择日期范围";
});
// 当日期变化时通知父组件
function handleValueChange(newValue: DateRange) {
if (newValue.start && newValue.end && onchange) {
onchange(formatDate(newValue.start), formatDate(newValue.end));
}
}
</script>
<Popover.Root>
<Popover.Trigger>
{#snippet child({ props })}
<Button
variant="outline"
class={cn(
"w-[260px] justify-start text-left font-normal",
!value.start && "text-muted-foreground",
className
)}
{...props}
>
<CalendarIcon class="mr-2 h-4 w-4" />
{displayText()}
</Button>
{/snippet}
</Popover.Trigger>
<Popover.Content class="w-auto p-0" align="start">
<RangeCalendar
{value}
onValueChange={handleValueChange}
numberOfMonths={2}
locale="zh-CN"
weekStartsOn={1}
/>
</Popover.Content>
</Popover.Root>