feat: 新增每日洞察卡片组件,提供消费和预算分析并集成AI洞察功能

This commit is contained in:
2026-01-29 08:48:44 +08:00
parent fbdd9cf0ae
commit 8aa881bcc1
2 changed files with 20 additions and 28 deletions

View File

@@ -136,7 +136,7 @@ export const DailyInsightCard: React.FC<DailyInsightCardProps> = ({
<div className={`daily-insight-card ${aiData ? 'daily-insight-card--ai' : ''}`}>
<div className="daily-insight__header">
<Icon icon={aiData ? "solar:magic-stick-3-bold-duotone" : "solar:stars-minimalistic-bold-duotone"} width="20" />
<span>{aiData ? 'AI 每日简报' : '每日简报'}</span>
<span>{aiData ? 'AI 消费简报' : '消费简报'}</span>
{aiData?.emoji && <span className="daily-insight__emoji">{aiData.emoji}</span>}
{isAiLoading && !aiData && <span className="daily-insight__loading-badge">AI ...</span>}
</div>

View File

@@ -21,6 +21,7 @@ import { HealthScoreModal } from '../../components/home/HealthScoreModal/HealthS
import { ContributionModal } from '../../components/common/ContributionGraph/ContributionModal';
import { DailyInsightCard } from '../../components/home/DailyInsightCard/DailyInsightCard'; // Import
import { getBudgets } from '../../services/budgetService'; // Import
import { toLocalDateString, isSameDay } from '../../utils/dateUtils';
import type { Account, Transaction, Category, Ledger, UserSettings } from '../../types';
@@ -58,6 +59,7 @@ function Home() {
const [todayTransactions, setTodayTransactions] = useState<Transaction[]>([]);
const [lastWeekSpend, setLastWeekSpend] = useState(0);
const [last7DaysSpend, setLast7DaysSpend] = useState<number[]>([]); // New: 最近7天支出
const [weekTransactions, setWeekTransactions] = useState<Transaction[]>([]); // New: 最近7天交易详情 (for AI)
useEffect(() => {
@@ -69,20 +71,9 @@ function Home() {
setLoading(true);
setError(null);
// Helper for local date string YYYY-MM-DD
const toLocalDate = (d: Date) => {
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
// Helper to check if two dates are the same day (using local time)
const isSameDay = (d1: Date, d2: Date) => {
return d1.getFullYear() === d2.getFullYear() &&
d1.getMonth() === d2.getMonth() &&
d1.getDate() === d2.getDate();
};
// Helper for local date string YYYY-MM-DD - REMOVED, using imported utils
const today = new Date();
const yesterday = new Date(today);
@@ -96,8 +87,8 @@ function Home() {
const rangeEnd = new Date(today);
rangeEnd.setDate(rangeEnd.getDate() + 1);
const rangeStartStr = toLocalDate(rangeStart);
const rangeEndStr = toLocalDate(rangeEnd);
const rangeStartStr = toLocalDateString(rangeStart);
const rangeEndStr = toLocalDateString(rangeEnd);
// Parallel fetch optimized: Single request for transaction history
const [accountsData, recentTxData, categoriesData, ledgersData, settingsData, budgetsData, streakData, rangeTxData] = await Promise.all([
@@ -136,13 +127,17 @@ function Home() {
// Last 7 Days Array (for chart)
const last7DaysSpendArray = [];
const weekTxList: Transaction[] = [];
for (let i = 6; i >= 0; i--) {
const d = new Date(today);
d.setDate(d.getDate() - i);
const dayTx = allRangeTx.filter(t => isSameDay(new Date(t.transactionDate), d));
last7DaysSpendArray.push(calculateTotalExpense(dayTx));
weekTxList.push(...dayTx);
}
setLast7DaysSpend(last7DaysSpendArray);
setWeekTransactions(weekTxList);
// Monthly Budget Stats
let mTotal = 0;
@@ -168,20 +163,17 @@ function Home() {
};
const { maxTransaction, topCategory, top3Categories, todayTransactionCount, avgDailySpend } = useMemo(() => {
if (todayTransactions.length === 0) return {
maxTransaction: undefined,
topCategory: undefined,
top3Categories: undefined,
todayTransactionCount: 0,
avgDailySpend: 0
};
// Basic stats
const todayCount = todayTransactions.length;
// Max Transaction
// Max Transaction (Keep as Today's max for immediate context)
const maxTx = [...todayTransactions].sort((a, b) => b.amount - a.amount)[0];
// Category aggregation
// Category aggregation - Use Week Transactions for better AI context
const sourceTransactions = weekTransactions.length > 0 ? weekTransactions : todayTransactions;
const catMap: Record<number, number> = {};
todayTransactions.forEach((t) => {
sourceTransactions.forEach((t) => {
catMap[t.categoryId] = (catMap[t.categoryId] || 0) + t.amount;
});
@@ -210,10 +202,10 @@ function Home() {
maxTransaction: maxTx,
topCategory: topCatName ? { name: topCatName, amount: catMap[Number(sortedCatIds[0])] } : undefined,
top3Categories: top3.length > 0 ? top3 : undefined,
todayTransactionCount: todayTransactions.length,
todayTransactionCount: todayCount,
avgDailySpend: avgDaily
};
}, [todayTransactions, categories, monthlyBudgetSpentTotal]);
}, [todayTransactions, weekTransactions, categories, monthlyBudgetSpentTotal]);
const handleQuickTransaction = () => {
navigate('/transactions?action=new');