fix(web): 修复弹窗裁切/宽度与日期选择器
This commit is contained in:
@@ -145,7 +145,7 @@ services:
|
|||||||
# 挂载 Docker Socket 以支持容器操作
|
# 挂载 Docker Socket 以支持容器操作
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
# 挂载仓库目录
|
# 挂载仓库目录
|
||||||
- /path/to/billai:/app
|
- ./:/app
|
||||||
working_dir: /app
|
working_dir: /app
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:9000/health"]
|
test: ["CMD", "curl", "-f", "http://localhost:9000/health"]
|
||||||
|
|||||||
@@ -199,7 +199,7 @@
|
|||||||
|
|
||||||
<!-- 分类详情弹窗 -->
|
<!-- 分类详情弹窗 -->
|
||||||
<Drawer.Root bind:open={dialogOpen}>
|
<Drawer.Root bind:open={dialogOpen}>
|
||||||
<Drawer.Content class="sm:max-w-4xl">
|
<Drawer.Content class="md:max-w-4xl">
|
||||||
<Drawer.Header>
|
<Drawer.Header>
|
||||||
<Drawer.Title class="flex items-center gap-2">
|
<Drawer.Title class="flex items-center gap-2">
|
||||||
<PieChartIcon class="h-5 w-5" />
|
<PieChartIcon class="h-5 w-5" />
|
||||||
|
|||||||
@@ -836,7 +836,7 @@
|
|||||||
|
|
||||||
<!-- 当日详情 Drawer -->
|
<!-- 当日详情 Drawer -->
|
||||||
<Drawer.Root bind:open={dialogOpen}>
|
<Drawer.Root bind:open={dialogOpen}>
|
||||||
<Drawer.Content class="sm:max-w-4xl">
|
<Drawer.Content class="md:max-w-4xl">
|
||||||
<Drawer.Header>
|
<Drawer.Header>
|
||||||
<Drawer.Title class="flex items-center gap-2">
|
<Drawer.Title class="flex items-center gap-2">
|
||||||
<Calendar class="h-5 w-5" />
|
<Calendar class="h-5 w-5" />
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
let { startDate = $bindable(), endDate = $bindable(), onchange, class: className }: Props = $props();
|
let { startDate = $bindable(), endDate = $bindable(), onchange, class: className }: Props = $props();
|
||||||
|
|
||||||
// 将 YYYY-MM-DD 字符串转换为 CalendarDate
|
// 将 YYYY-MM-DD 字符串转换为 CalendarDate
|
||||||
function parseDate(dateStr: string): DateValue | undefined {
|
function parseDate(dateStr?: string): DateValue | undefined {
|
||||||
if (!dateStr) return undefined;
|
if (!dateStr) return undefined;
|
||||||
const [year, month, day] = dateStr.split('-').map(Number);
|
const [year, month, day] = dateStr.split('-').map(Number);
|
||||||
return new CalendarDate(year, month, day);
|
return new CalendarDate(year, month, day);
|
||||||
@@ -101,7 +101,11 @@
|
|||||||
<Popover.Content class="w-auto p-0" align="start">
|
<Popover.Content class="w-auto p-0" align="start">
|
||||||
<RangeCalendar
|
<RangeCalendar
|
||||||
bind:value
|
bind:value
|
||||||
|
class="rounded-md border"
|
||||||
numberOfMonths={2}
|
numberOfMonths={2}
|
||||||
|
pagedNavigation={true}
|
||||||
|
fixedWeeks={true}
|
||||||
|
weekdayFormat="short"
|
||||||
locale="zh-CN"
|
locale="zh-CN"
|
||||||
weekStartsOn={1}
|
weekStartsOn={1}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
bind:ref
|
bind:ref
|
||||||
data-slot="dialog-content"
|
data-slot="dialog-content"
|
||||||
class={cn(
|
class={cn(
|
||||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-4 left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-0 gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg max-h-[calc(100dvh-2rem)] overflow-y-auto md:top-[50%] md:translate-y-[-50%] md:max-h-[85vh]",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
|
|||||||
@@ -20,14 +20,14 @@
|
|||||||
{#if isMobile.current}
|
{#if isMobile.current}
|
||||||
<Sheet.Content
|
<Sheet.Content
|
||||||
{side}
|
{side}
|
||||||
class={cn('max-h-[90vh] overflow-hidden flex flex-col', className)}
|
class={cn('max-h-[90vh] overflow-y-auto flex flex-col', className)}
|
||||||
>
|
>
|
||||||
<!-- 拖拽指示器 (移动端抽屉常见设计) -->
|
<!-- 拖拽指示器 (移动端抽屉常见设计) -->
|
||||||
<div class="mx-auto mt-2 h-1.5 w-12 shrink-0 rounded-full bg-muted"></div>
|
<div class="mx-auto mt-2 h-1.5 w-12 shrink-0 rounded-full bg-muted"></div>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</Sheet.Content>
|
</Sheet.Content>
|
||||||
{:else}
|
{:else}
|
||||||
<Dialog.Content class={cn('max-h-[85vh] overflow-hidden flex flex-col', className)}>
|
<Dialog.Content class={cn('max-h-[85vh] overflow-y-auto flex flex-col', className)}>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import Monitor from '@lucide/svelte/icons/monitor';
|
import Monitor from '@lucide/svelte/icons/monitor';
|
||||||
import Sun from '@lucide/svelte/icons/sun';
|
import Sun from '@lucide/svelte/icons/sun';
|
||||||
import Moon from '@lucide/svelte/icons/moon';
|
import Moon from '@lucide/svelte/icons/moon';
|
||||||
import type { ComponentType } from 'svelte';
|
import type { Component } from 'svelte';
|
||||||
|
|
||||||
export type ThemeMode = 'system' | 'light' | 'dark';
|
export type ThemeMode = 'system' | 'light' | 'dark';
|
||||||
|
|
||||||
export interface ThemeOption {
|
export interface ThemeOption {
|
||||||
label: string;
|
label: string;
|
||||||
icon: ComponentType;
|
icon: Component;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const themeConfig: Record<ThemeMode, ThemeOption> = {
|
export const themeConfig: Record<ThemeMode, ThemeOption> = {
|
||||||
|
|||||||
@@ -24,34 +24,43 @@
|
|||||||
let uploadResult: UploadResponse | null = $state(null);
|
let uploadResult: UploadResponse | null = $state(null);
|
||||||
let errorMessage = $state('');
|
let errorMessage = $state('');
|
||||||
|
|
||||||
|
type StatTrend = 'up' | 'down';
|
||||||
|
interface StatCard {
|
||||||
|
title: string;
|
||||||
|
value: string;
|
||||||
|
change: string;
|
||||||
|
trend: StatTrend;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
// 实时统计数据
|
// 实时统计数据
|
||||||
let stats = $state([
|
let stats = $state<StatCard[]>([
|
||||||
{
|
{
|
||||||
title: '本月支出',
|
title: '本月支出',
|
||||||
value: '¥0.00',
|
value: '¥0.00',
|
||||||
change: '+0%',
|
change: '+0%',
|
||||||
trend: 'up' as const,
|
trend: 'up',
|
||||||
description: '加载中...'
|
description: '加载中...'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '本月收入',
|
title: '本月收入',
|
||||||
value: '¥0.00',
|
value: '¥0.00',
|
||||||
change: '+0%',
|
change: '+0%',
|
||||||
trend: 'up' as const,
|
trend: 'up',
|
||||||
description: '加载中...'
|
description: '加载中...'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '待复核',
|
title: '待复核',
|
||||||
value: '0',
|
value: '0',
|
||||||
change: '+0%',
|
change: '+0%',
|
||||||
trend: 'up' as const,
|
trend: 'up',
|
||||||
description: '需要人工确认'
|
description: '需要人工确认'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '已处理账单',
|
title: '已处理账单',
|
||||||
value: '0',
|
value: '0',
|
||||||
change: '+0%',
|
change: '+0%',
|
||||||
trend: 'up' as const,
|
trend: 'up',
|
||||||
description: '累计处理记录'
|
description: '累计处理记录'
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@@ -66,62 +75,49 @@
|
|||||||
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||||
const previousMonth = `${lastMonth.getFullYear()}-${String(lastMonth.getMonth() + 1).padStart(2, '0')}`;
|
const previousMonth = `${lastMonth.getFullYear()}-${String(lastMonth.getMonth() + 1).padStart(2, '0')}`;
|
||||||
|
|
||||||
console.log('Current month:', currentMonth);
|
|
||||||
console.log('Previous month:', previousMonth);
|
|
||||||
|
|
||||||
// 获取月度统计数据
|
// 获取月度统计数据
|
||||||
const monthlyResponse = await fetchMonthlyStats();
|
const monthlyResponse = await fetchMonthlyStats();
|
||||||
console.log('Monthly response:', monthlyResponse);
|
|
||||||
const monthlyStats = monthlyResponse.data || [];
|
const monthlyStats = monthlyResponse.data || [];
|
||||||
console.log('Monthly stats:', monthlyStats);
|
|
||||||
|
|
||||||
// 获取待复核统计
|
// 获取待复核统计
|
||||||
const reviewResponse = await fetchReviewStats();
|
const reviewResponse = await fetchReviewStats();
|
||||||
console.log('Review response:', reviewResponse);
|
|
||||||
const reviewTotal = reviewResponse.data?.total || 0;
|
const reviewTotal = reviewResponse.data?.total || 0;
|
||||||
console.log('Review total:', reviewTotal);
|
|
||||||
|
|
||||||
// 获取已处理账单数量
|
// 获取已处理账单数量
|
||||||
const billsResponse = await fetchBills({ page_size: 1 });
|
const billsResponse = await fetchBills({ page_size: 1 });
|
||||||
console.log('Bills response:', billsResponse);
|
|
||||||
const billTotal = billsResponse.data?.total || 0;
|
const billTotal = billsResponse.data?.total || 0;
|
||||||
console.log('Bill total:', billTotal);
|
|
||||||
|
|
||||||
// 提取当月和上月的数据
|
// 提取当月和上月的数据
|
||||||
const currentData = monthlyStats.find(m => m.month === currentMonth);
|
const currentData = monthlyStats.find(m => m.month === currentMonth);
|
||||||
const previousData = monthlyStats.find(m => m.month === previousMonth);
|
const previousData = monthlyStats.find(m => m.month === previousMonth);
|
||||||
|
|
||||||
console.log('Current data:', currentData);
|
|
||||||
console.log('Previous data:', previousData);
|
|
||||||
|
|
||||||
// 计算支出变化百分比
|
// 计算支出变化百分比
|
||||||
const currentExpense = currentData?.expense || 0;
|
const currentExpense = currentData?.expense || 0;
|
||||||
const previousExpense = previousData?.expense || 0;
|
const previousExpense = previousData?.expense || 0;
|
||||||
const expenseChange = previousExpense > 0
|
const expenseChange = previousExpense > 0
|
||||||
? ((currentExpense - previousExpense) / previousExpense * 100).toFixed(1)
|
? (currentExpense - previousExpense) / previousExpense * 100
|
||||||
: 0;
|
: 0;
|
||||||
const expenseTrend = parseFloat(expenseChange.toString()) >= 0 ? 'up' : 'down';
|
const expenseTrend: StatTrend = expenseChange >= 0 ? 'up' : 'down';
|
||||||
|
|
||||||
// 计算收入变化百分比
|
// 计算收入变化百分比
|
||||||
const currentIncome = currentData?.income || 0;
|
const currentIncome = currentData?.income || 0;
|
||||||
const previousIncome = previousData?.income || 0;
|
const previousIncome = previousData?.income || 0;
|
||||||
const incomeChange = previousIncome > 0
|
const incomeChange = previousIncome > 0
|
||||||
? ((currentIncome - previousIncome) / previousIncome * 100).toFixed(1)
|
? (currentIncome - previousIncome) / previousIncome * 100
|
||||||
: 0;
|
: 0;
|
||||||
const incomeTrend = parseFloat(incomeChange.toString()) >= 0 ? 'up' : 'down';
|
const incomeTrend: StatTrend = incomeChange >= 0 ? 'up' : 'down';
|
||||||
|
|
||||||
// 格式化金额
|
// 格式化金额
|
||||||
const formatAmount = (amount: number) => {
|
const formatAmount = (amount: number) => {
|
||||||
return `¥${amount.toFixed(2)}`;
|
return `¥${amount.toFixed(2)}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatChange = (change: number | string) => {
|
const formatChange = (change: number) => {
|
||||||
const changeNum = typeof change === 'string' ? parseFloat(change) : change;
|
const sign = change >= 0 ? '+' : '';
|
||||||
const sign = changeNum >= 0 ? '+' : '';
|
return `${sign}${change.toFixed(1)}%`;
|
||||||
return `${sign}${changeNum.toFixed(1)}%`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const newStats = [
|
const newStats: StatCard[] = [
|
||||||
{
|
{
|
||||||
title: '本月支出',
|
title: '本月支出',
|
||||||
value: formatAmount(currentExpense),
|
value: formatAmount(currentExpense),
|
||||||
@@ -140,19 +136,18 @@
|
|||||||
title: '待复核',
|
title: '待复核',
|
||||||
value: reviewTotal.toString(),
|
value: reviewTotal.toString(),
|
||||||
change: '+0%',
|
change: '+0%',
|
||||||
trend: 'up' as const,
|
trend: 'up',
|
||||||
description: '需要人工确认'
|
description: '需要人工确认'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '已处理账单',
|
title: '已处理账单',
|
||||||
value: billTotal.toString(),
|
value: billTotal.toString(),
|
||||||
change: '+0%',
|
change: '+0%',
|
||||||
trend: 'up' as const,
|
trend: 'up',
|
||||||
description: '累计处理记录'
|
description: '累计处理记录'
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
console.log('New stats:', newStats);
|
|
||||||
stats = newStats;
|
stats = newStats;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load stats:', err);
|
console.error('Failed to load stats:', err);
|
||||||
|
|||||||
@@ -14,8 +14,8 @@
|
|||||||
|
|
||||||
let isLoading = $state(true);
|
let isLoading = $state(true);
|
||||||
let errorMessage = $state('');
|
let errorMessage = $state('');
|
||||||
let reviewStats: ReviewData | null = $state(null);
|
let reviewStats = $state<ReviewData | null>(null);
|
||||||
let allBills: CleanedBill[] = $state([]);
|
let allBills = $state<CleanedBill[]>([]);
|
||||||
let filterLevel = $state<'all' | 'HIGH' | 'LOW'>('all');
|
let filterLevel = $state<'all' | 'HIGH' | 'LOW'>('all');
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user