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:
@@ -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>
|
||||
Reference in New Issue
Block a user