feat: 新增 CommandPalette 和 HealthScoreModal 组件,优化应用布局并引入 Budget、Home 页面。

This commit is contained in:
2026-01-26 21:58:14 +08:00
parent bd3e2ba6a5
commit adef473fe9
8 changed files with 970 additions and 19 deletions

View File

@@ -0,0 +1,162 @@
/**
* HealthScoreModal Component
* Displays detailed financial health analysis
* Phase 3 Requirement: Emotional interface & Smart feedback
*/
import React, { useEffect, useState } from 'react';
import { Icon } from '@iconify/react';
import { formatCurrency } from '../../../utils/format';
import './HealthScoreModal.css';
interface HealthScoreModalProps {
isOpen: boolean;
onClose: () => void;
score: number;
totalAssets: number;
totalLiabilities: number;
todaySpend: number;
yesterdaySpend: number;
}
export const HealthScoreModal: React.FC<HealthScoreModalProps> = ({
isOpen,
onClose,
score,
totalAssets,
totalLiabilities,
todaySpend,
yesterdaySpend,
}) => {
const [animate, setAnimate] = useState(false);
useEffect(() => {
if (isOpen) {
setTimeout(() => setAnimate(true), 100);
} else {
setAnimate(false);
}
}, [isOpen]);
if (!isOpen) return null;
// Analysis Logic
const debtRatio = totalAssets > 0 ? (totalLiabilities / totalAssets) * 100 : 0;
let debtLevel = '优秀';
let debtColor = 'var(--color-success)';
if (debtRatio > 30) {
debtLevel = '一般';
debtColor = 'var(--color-warning)';
}
if (debtRatio > 60) {
debtLevel = '危险';
debtColor = 'var(--color-error)';
}
const spendDiff = todaySpend - yesterdaySpend;
const spendTrend = spendDiff > 0 ? 'up' : 'down';
const getLevel = (s: number) => {
if (s >= 90) return { label: '卓越', color: '#10b981', icon: 'solar:cup-star-bold-duotone' };
if (s >= 80) return { label: '优秀', color: '#3b82f6', icon: 'solar:medal-star-bold-duotone' };
if (s >= 60) return { label: '良好', color: '#f59e0b', icon: 'solar:check-circle-bold-duotone' };
return { label: '需努力', color: '#ef4444', icon: 'solar:danger-circle-bold-duotone' };
};
const level = getLevel(score);
return (
<div className="health-modal-overlay" onClick={onClose}>
<div
className={`health-modal-content ${animate ? 'animate-in' : ''}`}
onClick={(e) => e.stopPropagation()}
>
<button className="health-modal-close" onClick={onClose}>
<Icon icon="solar:close-circle-bold-duotone" width="24" />
</button>
<div className="health-modal-header">
<div className="health-score-ring-large">
<svg viewBox="0 0 100 100">
<circle
cx="50" cy="50" r="45"
fill="none"
stroke="var(--bg-tertiary)"
strokeWidth="8"
/>
<circle
cx="50" cy="50" r="45"
fill="none"
stroke={level.color}
strokeWidth="8"
strokeLinecap="round"
strokeDasharray={`${283 * (score / 100)} 283`}
transform="rotate(-90 50 50)"
className="health-ring-progress"
/>
</svg>
<div className="health-score-value-container">
<span className="health-score-value" style={{ color: level.color }}>{score}</span>
<span className="health-score-label"></span>
</div>
</div>
<div className="health-level-badge" style={{ backgroundColor: `${level.color}20`, color: level.color }}>
<Icon icon={level.icon} width="20" />
<span>{level.label}</span>
</div>
</div>
<div className="health-metrics-grid">
<div className="health-metric-card">
<div className="metric-icon debt">
<Icon icon="solar:card-2-bold-duotone" width="24" />
</div>
<div className="metric-info">
<span className="metric-label"></span>
<div className="metric-value-row">
<span className="metric-value">{debtRatio.toFixed(1)}%</span>
<span className="metric-status" style={{ color: debtColor }}>{debtLevel}</span>
</div>
</div>
</div>
<div className="health-metric-card">
<div className="metric-icon spend">
<Icon icon="solar:wallet-money-bold-duotone" width="24" />
</div>
<div className="metric-info">
<span className="metric-label"></span>
<div className="metric-value-row">
<span className="metric-value">{formatCurrency(todaySpend)}</span>
</div>
<span className={`metric-trend ${spendTrend}`}>
{spendTrend === 'up' ? '比昨天多' : '比昨天少'} {formatCurrency(Math.abs(spendDiff))}
</span>
</div>
</div>
</div>
<div className="health-suggestion-box">
<h4 className="suggestion-title">
<Icon icon="solar:lightbulb-bolt-bold-duotone" width="20" className="text-primary" />
</h4>
<p className="suggestion-text">
{score >= 80
? '您的财务状况非常健康!建议继续保持低负债率,并考虑适当增加投资比例以抵抗通胀。'
: score >= 60
? '财务状况良好,但还有提升空间。试着控制非必要支出,提高每月的储蓄比例。'
: '请注意控制支出!建议优先偿还高息债务,并审视近期的消费习惯。'}
</p>
</div>
<div className="health-actions">
<button className="health-action-btn primary" onClick={onClose}>
</button>
</div>
</div>
</div>
);
};