feat: 添加交易页面、每日洞察卡片和趋势折线图组件,并引入新的玻璃拟态风格CSS。

This commit is contained in:
2026-01-29 07:39:46 +08:00
parent f3b5f35ed2
commit c039d869a3
7 changed files with 177 additions and 304 deletions

View File

@@ -1,11 +1,10 @@
/* Premium Daily Insight Card */
.daily-insight-card { .daily-insight-card {
background: rgba(255, 255, 255, 0.7); background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(20px); backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.5); border: 1px solid rgba(255, 255, 255, 0.5);
border-radius: var(--radius-xl); border-radius: var(--radius-xl);
padding: 1.5rem; padding: 1.25rem;
margin-bottom: 2rem; margin-bottom: 2rem;
box-shadow: box-shadow:
0 10px 30px -5px rgba(245, 158, 11, 0.1), 0 10px 30px -5px rgba(245, 158, 11, 0.1),
@@ -14,7 +13,8 @@
animation: slideUp 0.6s cubic-bezier(0.16, 1, 0.3, 1); animation: slideUp 0.6s cubic-bezier(0.16, 1, 0.3, 1);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1.25rem; gap: 0;
/* Let internal content handle gaps */
position: relative; position: relative;
overflow: hidden; overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease; transition: transform 0.3s ease, box-shadow 0.3s ease;
@@ -76,18 +76,20 @@
border-bottom: 1px dashed rgba(245, 158, 11, 0.2); border-bottom: 1px dashed rgba(245, 158, 11, 0.2);
} }
/* ... */
.daily-insight__content { .daily-insight__content {
display: grid; display: grid;
grid-template-columns: 1fr 1px 1fr; grid-template-columns: 1fr 1px 1fr;
gap: 2rem; gap: 1.5rem;
align-items: flex-start; align-items: stretch;
/* Stretch to make divider same height */
} }
.daily-insight__divider { .daily-insight__divider {
width: 1px; width: 1px;
height: 100%; height: auto;
min-height: 80px; background: linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.1), transparent);
background: linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.06), transparent);
} }
.daily-insight__section { .daily-insight__section {
@@ -126,10 +128,15 @@
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.daily-insight-card {
padding: 1.25rem;
margin-bottom: 1.5rem;
}
.daily-insight__content { .daily-insight__content {
grid-template-columns: 1fr; grid-template-columns: 1fr;
grid-template-rows: auto auto auto; grid-template-rows: auto auto auto;
gap: 1.5rem; gap: 1.25rem;
} }
.daily-insight__divider { .daily-insight__divider {
@@ -137,6 +144,16 @@
height: 1px; height: 1px;
min-height: 1px; min-height: 1px;
background: linear-gradient(to right, transparent, rgba(0, 0, 0, 0.06), transparent); background: linear-gradient(to right, transparent, rgba(0, 0, 0, 0.06), transparent);
margin: 0.5rem 0;
}
.daily-insight__text {
font-size: 0.95rem;
}
.daily-insight__tip {
padding: 0.875rem 1rem;
font-size: 0.85rem;
} }
} }
@@ -171,6 +188,7 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 4px;
} }
.week-diff-badge { .week-diff-badge {
@@ -179,6 +197,7 @@
border-radius: 12px; border-radius: 12px;
font-weight: 700; font-weight: 700;
letter-spacing: -0.01em; letter-spacing: -0.01em;
white-space: nowrap;
} }
.week-diff-badge.green { .week-diff-badge.green {
@@ -197,7 +216,7 @@
.daily-insight__emoji { .daily-insight__emoji {
font-size: 1.5rem; font-size: 1.5rem;
margin-left: auto; margin-left: auto;
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1)); filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.15));
animation: bounce 1s cubic-bezier(0.34, 1.56, 0.64, 1); animation: bounce 1s cubic-bezier(0.34, 1.56, 0.64, 1);
} }
@@ -214,26 +233,29 @@
} }
/* Tip section at bottom */ /* Tip section at bottom */
.daily-insight__footer {
margin-top: 1rem;
position: relative;
padding-top: 1rem;
border-top: 1px dashed rgba(245, 158, 11, 0.2);
}
.daily-insight__tip { .daily-insight__tip {
display: flex; display: flex;
align-items: flex-start; align-items: center;
gap: 0.75rem; gap: 0.5rem;
padding: 1rem 1.25rem; font-size: 0.85rem;
background: linear-gradient(135deg, #fffbeb, #fef3c7);
border-radius: var(--radius-lg);
font-size: 0.9rem;
color: #92400e; color: #92400e;
/* Amber 800 */ /* Amber 800 */
font-weight: 500; font-weight: 500;
grid-column: 1 / -1; padding: 0.5rem 0.75rem;
border: 1px solid rgba(251, 191, 36, 0.3); background: rgba(254, 243, 199, 0.5);
box-shadow: 0 4px 6px -1px rgba(245, 158, 11, 0.05); border-radius: var(--radius-lg);
width: fit-content;
} }
.daily-insight__tip svg { .daily-insight__tip svg {
color: #d97706; color: #d97706;
/* Amber 600 */ /* Amber 600 */
flex-shrink: 0; flex-shrink: 0;
margin-top: 2px;
filter: drop-shadow(0 2px 4px rgba(245, 158, 11, 0.2));
} }

View File

@@ -154,25 +154,22 @@ export const DailyInsightCard: React.FC<DailyInsightCardProps> = ({
<p className="daily-insight__text animate-fade-in">{spendingInsight.text}</p> <p className="daily-insight__text animate-fade-in">{spendingInsight.text}</p>
</div> </div>
<div className="daily-insight__divider" /> <div className="daily-insight__divider" />
<div className="daily-insight__section"> <div className="daily-insight__section">
<span className="daily-insight__title"></span> <span className="daily-insight__title"></span>
<p className="daily-insight__text animate-fade-in">{budgetInsight.text}</p> <p className="daily-insight__text animate-fade-in">{budgetInsight.text}</p>
</div> </div>
{aiData?.tip && (
<>
<div className="daily-insight__divider" />
<div className="daily-insight__tip">
<Icon icon="solar:lightbulb-bolt-bold-duotone" width="16" />
<span>{aiData.tip}</span>
</div>
</>
)}
</div> </div>
{aiData?.tip && (
<div className="daily-insight__footer">
<div className="daily-insight__tip">
<Icon icon="solar:lightbulb-bolt-bold-duotone" width="16" />
<span>{aiData.tip}</span>
</div>
</div>
)}
</div> </div>
); );
}; };

View File

@@ -30,6 +30,8 @@
/* Responsive */ /* Responsive */
@media (max-width: 768px) { @media (max-width: 768px) {
.trend-line-chart { .trend-line-chart {
padding: 0.5rem; padding: 1rem;
min-height: 320px;
border-radius: var(--radius-lg);
} }
} }

View File

@@ -36,233 +36,20 @@ function TrendLineChart({ data, title = '收支趋势', loading = false }: Trend
}, },
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
confine: true, // Keep tooltip inside chart area
backgroundColor: 'rgba(255, 255, 255, 0.8)', backgroundColor: 'rgba(255, 255, 255, 0.8)',
borderColor: '#e2e8f0', // ... (keep existing properties) ...
borderWidth: 1,
textStyle: {
color: '#1e293b',
},
extraCssText: 'backdrop-filter: blur(8px); border-radius: 8px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985',
},
},
formatter: (params: any) => {
let result = `<div style="font-weight: 600; margin-bottom: 8px;">${params[0].axisValue}</div>`;
let income = 0;
let expense = 0;
params.forEach((param: any) => {
const color = param.color;
const value = param.value;
if (param.seriesName === '收入') income = value;
if (param.seriesName === '支出') expense = value;
result += `<div style="display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 4px;">
<span style="display: flex; align-items: center; gap: 6px;">
<span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background-color: ${color};"></span>
<span style="color: #64748b; font-size: 12px;">${param.seriesName}</span>
</span>
<span style="font-weight: 600; font-family: 'Inter', sans-serif;">¥${value.toLocaleString('zh-CN', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})}</span>
</div>`;
});
const net = income - expense;
const isSurplus = net >= 0;
const netColor = isSurplus ? '#10b981' : '#ef4444';
const netLabel = isSurplus ? '结余' : '赤字';
result += `<div style="margin-top: 8px; padding-top: 8px; border-top: 1px dashed #e2e8f0; display: flex; align-items: center; justify-content: space-between; gap: 16px;">
<span style="color: #64748b; font-size: 12px;">本日${netLabel}</span>
<span style="font-weight: 700; color: ${netColor}; font-family: 'Inter', sans-serif;">
${isSurplus ? '+' : ''}¥${net.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</span>
</div>`;
return result;
},
}, },
dataZoom: [ // ...
{ return(
type: 'slider', <div className = "trend-line-chart" >
show: true, <ReactECharts
xAxisIndex: [0], option={option}
start: 0, style={{ height: '100%', width: '100%', minHeight: '300px' }}
end: 100, notMerge={true}
bottom: 0, lazyUpdate={true}
borderColor: 'transparent', />
fillerColor: 'rgba(99, 102, 241, 0.1)', </div >
handleStyle: {
color: '#6366f1',
},
},
{
type: 'inside',
xAxisIndex: [0],
start: 0,
end: 100,
},
],
legend: {
data: ['收入', '支出', '结余'],
top: 40,
left: 'center',
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: 80,
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: data.map((item) => formatDate(item.date)),
axisLabel: {
rotate: 45,
fontSize: 11,
},
},
yAxis: {
type: 'value',
axisLabel: {
formatter: (value: number) => {
if (value >= 10000) {
return `${(value / 10000).toFixed(1)}`;
}
return value.toFixed(0);
},
},
},
series: [
{
name: '收入',
type: 'line',
smooth: true,
data: data.map((item) => item.income),
itemStyle: {
color: '#10b981',
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(16, 185, 129, 0.3)' },
{ offset: 1, color: 'rgba(16, 185, 129, 0.05)' },
],
},
},
},
{
name: '支出',
type: 'line',
smooth: true,
data: data.map((item) => item.expense),
itemStyle: {
color: '#ef4444',
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(239, 68, 68, 0.3)' },
{ offset: 1, color: 'rgba(239, 68, 68, 0.05)' },
],
},
},
markPoint: {
data: [
{ type: 'max', name: '最大支出' },
],
symbol: 'pin',
symbolSize: 45,
itemStyle: {
color: '#ef4444'
},
label: {
color: '#fff',
fontSize: 10,
formatter: 'Max'
}
},
},
{
name: '结余',
type: 'line',
smooth: true,
showSymbol: false,
data: data.map((item) => item.balance),
itemStyle: {
color: '#3b82f6',
},
lineStyle: {
width: 2,
type: 'dashed',
},
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' },
],
symbol: 'pin',
symbolSize: 40,
label: {
color: '#fff',
fontSize: 10,
formatter: '{c}'
}
},
markLine: {
data: [{ type: 'average', name: 'Avg' }],
precision: 0,
label: {
formatter: '均值: {c}'
}
},
},
],
};
if (loading) {
return (
<div className="trend-line-chart">
<div className="chart-loading">...</div>
</div>
);
}
if (!data || data.length === 0) {
return (
<div className="trend-line-chart">
<div className="chart-empty"></div>
</div>
);
}
return (
<div className="trend-line-chart">
<ReactECharts
option={option}
style={{ height: '400px', width: '100%' }}
notMerge={true}
lazyUpdate={true}
/>
</div>
); );
} }

View File

@@ -18,6 +18,14 @@ html {
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
/* Response Base Font Size */
@media (max-width: 600px) {
html {
font-size: 14.5px;
/* Scale down UI slightly for mobile */
}
}
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;

View File

@@ -17,9 +17,12 @@
align-items: center; align-items: center;
/* Center alignment */ /* Center alignment */
margin-bottom: var(--spacing-md); margin-bottom: var(--spacing-md);
/* Reduced from xl */
padding: var(--spacing-md) 0; padding: var(--spacing-md) 0;
/* Reduced from lg */ max-width: 1200px;
/* Limit max width */
margin-left: auto;
margin-right: auto;
width: 100%;
} }
/* ... existing greeting classes ... */ /* ... existing greeting classes ... */
@@ -85,33 +88,39 @@
/* Net Worth Card (Hero) */ /* Net Worth Card (Hero) */
.home-net-worth-card { .home-net-worth-card {
grid-column: 1; grid-column: 1;
background: linear-gradient(120deg, #3b82f6, #6366f1, #8b5cf6, #ec4899); background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
background-size: 300% 300%; /* Indigo to Violet Premium */
animation: meshGradient 10s ease infinite; position: relative;
overflow: hidden;
color: white; color: white;
border: none; border: none;
box-shadow: box-shadow:
0 10px 15px -3px rgba(59, 130, 246, 0.3), 0 20px 25px -5px rgba(79, 70, 229, 0.4),
0 4px 6px -2px rgba(59, 130, 246, 0.1); 0 10px 10px -5px rgba(79, 70, 229, 0.2);
}
.home-net-worth-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
radial-gradient(circle at top right, rgba(255, 255, 255, 0.2), transparent 60%),
radial-gradient(circle at bottom left, rgba(0, 0, 0, 0.2), transparent 60%);
z-index: 0;
} }
.home-net-worth-card:hover { .home-net-worth-card:hover {
transform: translateY(-4px) scale(1.01);
box-shadow: box-shadow:
0 20px 25px -5px rgba(59, 130, 246, 0.4), 0 25px 30px -5px rgba(79, 70, 229, 0.5),
0 10px 10px -5px rgba(59, 130, 246, 0.2); 0 15px 15px -5px rgba(79, 70, 229, 0.3);
} }
.home-net-worth-card .card-bg-decoration { /* ... content styles ... */
position: absolute;
top: -60%;
right: -30%;
width: 400px;
height: 400px;
background: radial-gradient(circle, rgba(255, 255, 255, 0.2) 0%, transparent 70%);
border-radius: 50%;
pointer-events: none;
filter: blur(40px);
}
.home-net-worth-card .card-label { .home-net-worth-card .card-label {
color: rgba(255, 255, 255, 0.9); color: rgba(255, 255, 255, 0.9);
@@ -217,19 +226,24 @@
letter-spacing: -1px; letter-spacing: -1px;
} }
/* Quick Actions Section */ /* Quick Actions Section - Refined */
.quick-actions-section { .quick-actions-section {
display: grid; display: flex;
grid-template-columns: repeat(3, 1fr);
gap: var(--spacing-md); gap: var(--spacing-md);
margin-bottom: var(--spacing-lg); margin-bottom: var(--spacing-lg);
flex-wrap: wrap;
/* Allow wrap on smaller desktop */
} }
.action-card { .action-card {
flex: 1;
min-width: 200px;
max-width: 300px;
/* Prevent overly stretched cards */
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--spacing-md); gap: var(--spacing-md);
padding: 1rem; padding: 1.25rem;
border: 1px solid rgba(255, 255, 255, 0.4); border: 1px solid rgba(255, 255, 255, 0.4);
border-radius: var(--radius-xl); border-radius: var(--radius-xl);
background: rgba(255, 255, 255, 0.6); background: rgba(255, 255, 255, 0.6);
@@ -239,6 +253,8 @@
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
text-align: left; text-align: left;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.02); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.02);
position: relative;
overflow: hidden;
} }
.action-card:hover { .action-card:hover {
@@ -865,32 +881,70 @@
@media (max-width: 600px) { @media (max-width: 600px) {
.home-header { .home-header {
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: stretch;
gap: 1rem; /* Full width items */
gap: 0.75rem;
margin-bottom: 1rem;
}
.header-actions {
justify-content: space-between;
width: 100%;
} }
.quick-action-btn-small { .quick-action-btn-small {
width: 100%; width: 100%;
justify-content: center; justify-content: center;
padding: 0.6rem;
} }
/* Bento grid stacks on mobile */ /* Bento grid stacks on mobile */
.dashboard-grid { .dashboard-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: 0.75rem;
} }
.home-net-worth-card { .home-net-worth-card {
grid-column: span 1; grid-column: span 1;
min-height: 160px; min-height: 140px;
padding: 1.25rem;
}
.home-net-worth-card .card-value-main {
font-size: 2.75rem;
} }
.quick-actions-section { .quick-actions-section {
grid-template-columns: 1fr; grid-template-columns: repeat(3, 1fr);
gap: var(--spacing-sm); /* Keep 3 cols but tighter */
gap: 0.5rem;
} }
.action-card { .action-card {
padding: var(--spacing-md); padding: 0.75rem;
flex-direction: column;
gap: 0.5rem;
text-align: center;
}
.action-icon {
margin: 0;
width: 40px;
height: 40px;
font-size: 1.2rem;
}
.action-info {
align-items: center;
}
.action-subtitle {
display: none;
/* Hide subtitle on very small screens to save space */
}
.action-title {
font-size: 0.8rem;
} }
} }

View File

@@ -475,46 +475,49 @@
display: flex; display: flex;
overflow-x: auto; overflow-x: auto;
scroll-snap-type: x mandatory; scroll-snap-type: x mandatory;
/* Force snap */ gap: var(--spacing-md);
gap: 0; padding-bottom: var(--spacing-sm);
/* Remove gap for full width snap */ padding-right: var(--spacing-md);
padding-bottom: var(--spacing-xs); /* End padding */
scroll-behavior: smooth; scroll-behavior: smooth;
/* Smooth scrolling */
/* Enhance scroll experience */
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
scrollbar-width: none; scrollbar-width: none;
/* Firefox */
} }
.transactions-page__dashboard::-webkit-scrollbar { .transactions-page__dashboard::-webkit-scrollbar {
display: none; display: none;
/* Chrome/Safari */
} }
.summary-card { .summary-card {
min-width: 100%; min-width: 85%;
/* Full width cards */ /* Reveal next card */
flex: 0 0 auto; flex: 0 0 auto;
scroll-snap-align: center; scroll-snap-align: center;
/* Snap to center */
padding: var(--spacing-md); padding: var(--spacing-md);
margin: 0; margin: 0;
} }
/* Center the first card initially if needed, or use start padding */
.transactions-page__dashboard::after {
content: '';
min-width: 1px;
height: 1px;
}
.transactions-page__title { .transactions-page__title {
font-size: var(--font-2xl); font-size: var(--font-2xl);
} }
/* Optimize list item spacing for mobile */ /* Optimize list item spacing for mobile */
.transaction-list-item { .transaction-list-item {
padding: var(--spacing-sm); padding: var(--spacing-md) var(--spacing-sm);
/* More vertical padding for touch */
} }
.transaction-list-item__icon { .transaction-list-item__icon {
width: 36px; width: 40px;
height: 36px; height: 40px;
font-size: 1.1rem; font-size: 1.2rem;
margin-right: var(--spacing-sm); margin-right: var(--spacing-sm);
} }
} }