feat: 新增首页和账本管理页面,并引入导航、卡片、图表等多个UI组件及玻璃拟物化布局样式。

This commit is contained in:
2026-01-30 14:57:08 +08:00
parent b404072b3b
commit d919a42d15
10 changed files with 839 additions and 309 deletions

View File

@@ -28,4 +28,8 @@
background: rgba(0, 0, 0, 0.04);
padding: 4px 10px;
border-radius: 99px;
}
.dark .chart-subtitle {
background: rgba(255, 255, 255, 0.1);
}

View File

@@ -17,7 +17,6 @@
padding: 0 var(--spacing-xl);
/* Increased horizontal padding */
/* Glassmorphism Header */
/* Glassmorphism Header */
background: var(--glass-bg);
backdrop-filter: blur(24px);
@@ -34,6 +33,13 @@
transition: all 0.3s ease;
}
/* Dark Mode Header */
.dark .app-header {
background: rgba(30, 32, 45, 0.7);
border-bottom-color: rgba(255, 255, 255, 0.08);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
}
/* Brand Section Wrapper */
.brand-wrapper {
display: flex;

View File

@@ -30,6 +30,13 @@
transition: transform 0.3s ease;
}
/* Dark Mode Mobile Nav */
.dark .navigation {
background: rgba(30, 32, 45, 0.85);
border-top-color: rgba(255, 255, 255, 0.08);
box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.3);
}
.navigation-toggle {
display: none;
}
@@ -148,6 +155,13 @@
border-top: none;
}
/* Dark Mode Desktop Sidebar */
.dark .navigation {
background: rgba(30, 32, 45, 0.6);
border-right-color: rgba(255, 255, 255, 0.08);
box-shadow: 5px 0 30px -10px rgba(0, 0, 0, 0.3);
}
/* Reset hidden state for sidebar */
.navigation--hidden {
transform: none;
@@ -215,6 +229,12 @@
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.03);
}
.dark .navigation-link:hover {
background: rgba(255, 255, 255, 0.08);
color: white;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
/* Active State - Premium Capsule Style */
.navigation-link--active {
background: linear-gradient(135deg, var(--accent-primary) 0%, var(--accent-secondary) 100%);

View File

@@ -75,6 +75,12 @@ const navItems: NavItem[] = [
label: '汇率',
ariaLabel: '汇率管理 - 管理货币汇率',
},
{
path: '/ledgers/manage',
icon: 'solar:notebook-bold-duotone',
label: '账本',
ariaLabel: '账本管理 - 切换和管理账本',
},
{
path: '/settings',
icon: 'solar:settings-bold-duotone',

View File

@@ -5,13 +5,14 @@
height: 100%;
display: flex;
flex-direction: column;
/* Styles are inherited from .bento-card container */
}
.heatmap-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
margin-bottom: 0.75rem;
}
.heatmap-title {
@@ -49,15 +50,17 @@
font-size: 0.9rem;
}
/* Graph Container */
.graph-scroll-container {
flex: 1;
display: flex;
align-items: center;
overflow-x: auto;
padding-bottom: 0.25rem;
padding: 0 4px;
margin: 0 -4px;
scrollbar-width: none;
/* Hide scrollbar for cleaner look */
mask-image: linear-gradient(to right, transparent, black 10%, black 90%, transparent);
mask-image: linear-gradient(to right, transparent, black 5%, black 95%, transparent);
-webkit-mask-image: linear-gradient(to right, transparent, black 5%, black 95%, transparent);
}
@@ -67,8 +70,178 @@
.contribution-graph-compact {
display: flex;
gap: 3px;
gap: 4px;
/* Space between weeks */
min-width: 100%;
justify-content: flex-end;
/* Align to right usually looks better for 'latest' days */
/* Align to right */
height: 100%;
/* Ensure it takes available height */
}
.week-column-compact {
display: flex;
flex-direction: column;
gap: 4px;
/* Space between days */
justify-content: center;
}
/* Day Cells */
.day-cell-compact {
width: 12px;
height: 12px;
border-radius: 3px;
background-color: rgba(0, 0, 0, 0.05);
/* L0 Color (Clean placeholder) */
transition: all 0.3s ease;
}
.dark .day-cell-compact {
background-color: rgba(255, 255, 255, 0.05);
/* L0 Dark */
}
/* Hover effect on cells */
.day-cell-compact:hover {
transform: scale(1.4);
z-index: 10;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border-radius: 4px;
}
/* Contribution Levels */
.day-cell-compact.level-1 {
background-color: #fcd34d;
/* Amber 300 */
}
.day-cell-compact.level-2 {
background-color: #fbbf24;
/* Amber 400 */
}
.day-cell-compact.level-3 {
background-color: #f59e0b;
/* Amber 500 */
}
.day-cell-compact.level-4 {
background-color: #d97706;
/* Amber 600 */
box-shadow: 0 0 8px rgba(245, 158, 11, 0.4);
/* Glow for max level */
}
/* Dark Mode Level Adjustments */
.dark .day-cell-compact.level-1 {
background-color: rgba(251, 191, 36, 0.3);
}
.dark .day-cell-compact.level-2 {
background-color: rgba(251, 191, 36, 0.5);
}
.dark .day-cell-compact.level-3 {
background-color: rgba(251, 191, 36, 0.7);
}
.dark .day-cell-compact.level-4 {
background-color: rgba(251, 191, 36, 0.9);
box-shadow: 0 0 10px rgba(251, 191, 36, 0.3);
}
/* Footer Layout */
.heatmap-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 1rem;
border-top: 1px solid rgba(0, 0, 0, 0.04);
padding-top: 0.75rem;
}
.dark .heatmap-footer {
border-top-color: rgba(255, 255, 255, 0.05);
}
.streak-summary {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.75rem;
color: var(--text-tertiary);
}
.streak-summary strong {
color: var(--text-primary);
font-weight: 700;
}
.divider {
color: var(--text-tertiary);
opacity: 0.5;
}
/* Legend */
.graph-legend-compact {
display: flex;
align-items: center;
gap: 4px;
font-size: 0.7rem;
color: var(--text-tertiary);
}
.legend-cell {
width: 10px;
height: 10px;
border-radius: 2px;
}
.legend-cell.level-0 {
background-color: rgba(0, 0, 0, 0.05);
}
.dark .legend-cell.level-0 {
background-color: rgba(255, 255, 255, 0.05);
}
.legend-cell.level-2 {
background-color: #fbbf24;
}
.dark .legend-cell.level-2 {
background-color: rgba(251, 191, 36, 0.5);
}
.legend-cell.level-4 {
background-color: #d97706;
}
.dark .legend-cell.level-4 {
background-color: rgba(251, 191, 36, 0.9);
}
/* Loading State */
.heatmap-loading {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-tertiary);
font-size: 0.85rem;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%,
100% {
opacity: 0.6;
}
50% {
opacity: 1;
}
}

View File

@@ -9,6 +9,11 @@
background: linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.7));
}
/* Dark Mode Override */
.dark .daily-insight-card {
background: linear-gradient(135deg, rgba(30, 32, 45, 0.9), rgba(30, 30, 35, 0.7));
}
/* AI Variant Styling */
.daily-insight-card--ai {
background: linear-gradient(135deg, rgba(255, 255, 255, 0.95), rgba(254, 252, 248, 0.9));
@@ -18,6 +23,14 @@
inset 0 0 0 1px rgba(255, 255, 255, 0.8);
}
.dark .daily-insight-card--ai {
background: linear-gradient(135deg, rgba(35, 40, 50, 0.9), rgba(30, 35, 40, 0.8));
border-color: rgba(245, 158, 11, 0.2);
box-shadow:
0 10px 40px -10px rgba(245, 158, 11, 0.1),
inset 0 0 0 1px rgba(255, 255, 255, 0.05);
}
.daily-insight-card--ai:hover {
border-color: rgba(245, 158, 11, 0.5);
box-shadow:
@@ -25,6 +38,12 @@
inset 0 0 0 1px rgba(255, 255, 255, 0.9);
}
.dark .daily-insight-card--ai:hover {
box-shadow:
0 20px 50px -12px rgba(245, 158, 11, 0.15),
inset 0 0 0 1px rgba(255, 255, 255, 0.1);
}
.daily-insight__header {
display: flex;
align-items: center;
@@ -99,86 +118,58 @@
border-radius: 6px;
}
.daily-insight__highlight--success {
color: #10b981;
background: rgba(16, 185, 129, 0.1);
}
.daily-insight__highlight--warning {
color: #f59e0b;
background: rgba(245, 158, 11, 0.1);
}
.daily-insight__highlight--danger {
color: #ef4444;
background: rgba(239, 68, 68, 0.1);
}
.week-diff-badge {
font-size: 0.75rem;
font-weight: 600;
padding: 2px 8px;
border-radius: 99px;
}
.week-diff-badge.green {
color: #10b981;
background: rgba(16, 185, 129, 0.1);
}
.week-diff-badge.red {
color: #ef4444;
background: rgba(239, 68, 68, 0.1);
.dark .daily-insight__highlight {
background: rgba(255, 255, 255, 0.1);
color: var(--text-primary);
}
.daily-insight__footer {
margin-top: auto;
display: flex;
align-items: center;
justify-content: flex-start;
padding-top: 1rem;
gap: 0.5rem;
}
.tip-icon {
color: var(--text-tertiary);
flex-shrink: 0;
margin-top: 2px;
}
.daily-insight__tip {
display: flex;
align-items: center;
gap: 0.75rem;
font-size: 0.85rem;
color: var(--text-secondary);
background: rgba(255, 255, 255, 0.5);
padding: 8px 12px;
border-radius: 12px;
border: 1px solid rgba(0, 0, 0, 0.04);
font-style: italic;
line-height: 1.4;
}
/* Loading Skeleton */
.insight-skeleton {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-tertiary);
font-size: 0.9rem;
gap: 0.5rem;
}
/* Animations */
@keyframes pulse {
0% {
opacity: 0.6;
0%,
100% {
opacity: 1;
}
50% {
opacity: 1;
}
100% {
opacity: 0.6;
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in {
animation: fadeIn 0.4s ease-out forwards;
}
/* Mobile Adjustments */
@media (max-width: 768px) {
.daily-insight__content {
flex-direction: column;

View File

@@ -37,6 +37,11 @@
text-align: left;
}
.dark .qa-btn {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.05);
}
.qa-btn:hover {
background: white;
transform: translateX(4px);
@@ -44,6 +49,12 @@
border-color: rgba(0, 0, 0, 0.08);
}
.dark .qa-btn:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.qa-icon-wrapper {
width: 40px;
height: 40px;
@@ -60,6 +71,10 @@
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.03);
}
.dark .qa-icon-wrapper {
background: rgba(255, 255, 255, 0.1);
}
.qa-btn:hover .qa-icon-wrapper {
background: var(--qa-color);
color: white;

View File

@@ -106,6 +106,19 @@
inset 0 0 0 1px rgba(255, 255, 255, 0.4);
}
/* Dark Mode Overrides for Bento Card */
.dark .bento-card {
background: rgba(30, 32, 45, 0.6);
/* Deep blue-grey base */
border-color: rgba(255, 255, 255, 0.08);
/* Fainter border */
box-shadow:
0 10px 30px -10px rgba(0, 0, 0, 0.4),
/* Stronger shadow for depth */
inset 0 0 0 1px rgba(255, 255, 255, 0.03);
/* Faint inset highlight */
}
.bento-card:hover {
transform: translateY(-6px) scale(1.01);
background: rgba(255, 255, 255, 0.85);
@@ -116,6 +129,14 @@
z-index: 10;
}
.dark .bento-card:hover {
background: rgba(40, 42, 55, 0.8);
box-shadow:
0 25px 50px -12px rgba(0, 0, 0, 0.6),
inset 0 0 0 1px rgba(255, 255, 255, 0.08);
border-color: rgba(255, 255, 255, 0.15);
}
/* Specific Card Styles Overrides for Bento */
.bento-card.dark-glass {
background: rgba(30, 30, 35, 0.85);
@@ -288,4 +309,171 @@
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
/* Transaction List Compact (Bento Style) */
.transaction-list-compact {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.transaction-row {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.75rem;
border-radius: 16px;
background: rgba(255, 255, 255, 0.4);
border: 1px solid rgba(255, 255, 255, 0.4);
transition: all 0.2s ease;
cursor: pointer;
}
/* Dark Mode row style */
.dark .transaction-row {
background: rgba(255, 255, 255, 0.03);
border-color: rgba(255, 255, 255, 0.05);
}
.transaction-row:hover {
background: rgba(255, 255, 255, 0.7);
transform: translateX(4px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
.dark .transaction-row:hover {
background: rgba(255, 255, 255, 0.08);
/* Subtle highlight in dark mode */
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.transaction-icon {
width: 40px;
height: 40px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
flex-shrink: 0;
}
.transaction-icon.expense {
background: rgba(239, 68, 68, 0.1);
color: #ef4444;
}
.transaction-icon.income {
background: rgba(16, 185, 129, 0.1);
color: #10b981;
}
.transaction-details {
flex: 1;
display: flex;
flex-direction: column;
gap: 2px;
overflow: hidden;
}
.transaction-category {
font-size: 0.95rem;
font-weight: 600;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.transaction-note-compact {
font-size: 0.8rem;
color: var(--text-tertiary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.transaction-amount-compact {
font-family: 'Outfit', sans-serif;
font-weight: 700;
font-size: 1rem;
white-space: nowrap;
}
.transaction-amount-compact.expense {
color: var(--text-primary);
}
.transaction-amount-compact.income {
color: #10b981;
}
.empty-state-compact {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem;
color: var(--text-tertiary);
gap: 0.5rem;
font-size: 0.9rem;
}
/* Floating Voice Button - Premium Style */
.fab-voice-btn {
position: fixed;
bottom: 2rem;
right: 2rem;
width: 64px;
height: 64px;
border-radius: 50%;
border: none;
background: linear-gradient(135deg, var(--accent-primary) 0%, var(--accent-secondary) 100%);
color: white;
box-shadow:
0 10px 25px -5px rgba(59, 130, 246, 0.5),
/* Fallback for accent-rgb if not defined */
0 8px 10px -6px rgba(59, 130, 246, 0.3);
cursor: pointer;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
outline: none;
}
.fab-voice-btn:hover {
transform: translateY(-4px) scale(1.05);
box-shadow:
0 20px 40px -5px rgba(59, 130, 246, 0.6),
0 12px 16px -8px rgba(59, 130, 246, 0.4);
}
.fab-voice-btn::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 50%;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3);
pointer-events: none;
}
.fab-voice-btn:active {
transform: translateY(0) scale(0.95);
box-shadow:
0 5px 15px -3px rgba(59, 130, 246, 0.5);
}
@media (max-width: 640px) {
.fab-voice-btn {
bottom: 1.5rem;
right: 1.5rem;
width: 56px;
height: 56px;
}
}

View File

@@ -1,17 +1,21 @@
/**
* LedgerManagePage Styles
* LedgerManagePage View Styles
* Requirements: 3.6-3.9
* Style: Ultra Premium Glass (Bento Grid)
*/
.ledger-manage-page {
min-height: 100vh;
background-color: #f9fafb;
/* Background is managed by global theme (or Layout) */
padding: 1.5rem;
}
/* Header */
.ledger-manage-page__header {
margin-bottom: var(--spacing-xl);
display: flex;
justify-content: space-between;
align-items: center;
}
.ledger-manage-page__title {
@@ -27,38 +31,74 @@
letter-spacing: -0.5px;
}
/* Create Button (Premium FAB/Action) */
.ledger-manage-page__create-btn {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1.25rem;
background: linear-gradient(135deg, var(--accent-primary) 0%, var(--accent-secondary) 100%);
color: white;
border: none;
border-radius: 99px;
font-weight: 600;
font-size: 0.95rem;
cursor: pointer;
box-shadow: 0 4px 12px rgba(var(--accent-rgb), 0.3);
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.ledger-manage-page__create-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(var(--accent-rgb), 0.4);
}
.ledger-manage-page__create-btn:active {
transform: translateY(0) scale(0.96);
}
/* Tabs */
.ledger-manage-page__tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 1.5rem;
border-bottom: 2px solid #e5e7eb;
margin-bottom: 2rem;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
padding-bottom: 0.5rem;
}
.dark .ledger-manage-page__tabs {
border-bottom-color: rgba(255, 255, 255, 0.05);
}
.ledger-manage-page__tab {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
background: none;
padding: 0.75rem 1.25rem;
background: transparent;
border: none;
border-bottom: 2px solid transparent;
margin-bottom: -2px;
font-size: 1rem;
font-weight: 500;
color: #6b7280;
border-radius: 12px;
font-size: 0.95rem;
font-weight: 600;
color: var(--text-tertiary);
cursor: pointer;
transition: all 0.2s;
transition: all 0.2s ease;
position: relative;
}
.ledger-manage-page__tab:hover {
color: #3b82f6;
color: var(--text-primary);
background: rgba(0, 0, 0, 0.03);
}
.dark .ledger-manage-page__tab:hover {
background: rgba(255, 255, 255, 0.05);
}
.ledger-manage-page__tab--active {
color: #3b82f6;
border-bottom-color: #3b82f6;
color: var(--accent-primary);
background: rgba(var(--accent-rgb), 0.08) !important;
}
/* Error Message */
@@ -67,53 +107,301 @@
align-items: center;
gap: 0.75rem;
padding: 1rem;
background-color: #fef2f2;
border: 1px solid #fecaca;
border-radius: 0.5rem;
color: #dc2626;
background-color: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.2);
border-radius: 12px;
color: #ef4444;
margin-bottom: 1.5rem;
backdrop-filter: blur(8px);
}
.ledger-manage-page__error-close {
margin-left: auto;
background: none;
border: none;
color: #dc2626;
color: currentColor;
cursor: pointer;
padding: 0.25rem;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0.25rem;
transition: background-color 0.2s;
}
.ledger-manage-page__error-close:hover {
background-color: #fee2e2;
background-color: rgba(0, 0, 0, 0.05);
}
/* Content */
/* Content Area */
.ledger-manage-page__content {
min-height: 400px;
}
/* Loading State */
.ledger-manage-page__loading {
/* List Layout (Grid) */
.ledger-manage-page__list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
gap: 1.5rem;
}
/* Ledger Card - Premium Style */
.ledger-manage-card {
display: flex;
flex-direction: column;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.5);
border-radius: 20px;
box-shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.05);
overflow: hidden;
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.dark .ledger-manage-card {
background: rgba(30, 32, 45, 0.6);
border-color: rgba(255, 255, 255, 0.08);
box-shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.4);
}
.ledger-manage-card:hover {
transform: translateY(-8px);
box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.1);
border-color: rgba(var(--accent-rgb), 0.3);
}
.dark .ledger-manage-card:hover {
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.6);
border-color: rgba(255, 255, 255, 0.15);
}
/* Card Cover */
.ledger-manage-card__cover {
height: 140px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
.ledger-manage-card__cover-image {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.5s ease;
}
.ledger-manage-card:hover .ledger-manage-card__cover-image {
transform: scale(1.05);
}
.ledger-manage-card__cover-placeholder {
color: rgba(255, 255, 255, 0.9);
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
backdrop-filter: blur(4px);
/* Slight texture */
}
/* Card Info */
.ledger-manage-card__info {
padding: 1.25rem;
flex: 1;
}
.ledger-manage-card__name {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 1.2rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 0.5rem;
}
.ledger-manage-card__default-badge {
display: inline-flex;
align-items: center;
padding: 0.2rem 0.6rem;
background: linear-gradient(135deg, var(--accent-primary) 0%, var(--accent-secondary) 100%);
color: white;
font-size: 0.7rem;
font-weight: 600;
border-radius: 99px;
box-shadow: 0 2px 6px rgba(var(--accent-rgb), 0.2);
}
.ledger-manage-card__meta {
display: flex;
flex-direction: column;
gap: 0.25rem;
font-size: 0.85rem;
color: var(--text-secondary);
}
.ledger-manage-card__theme {
display: inline-flex;
align-items: center;
gap: 4px;
}
.ledger-manage-card__theme::before {
content: '';
display: block;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: currentColor;
opacity: 0.7;
}
/* Card Actions */
.ledger-manage-card__actions {
display: flex;
gap: 0.5rem;
padding: 0 1.25rem 1.25rem 1.25rem;
}
.ledger-manage-card__action-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.625rem 1rem;
border: 1px solid rgba(0, 0, 0, 0.08);
/* Clean default border */
border-radius: 12px;
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);
background-color: rgba(255, 255, 255, 0.5);
/* Semi-transparent */
color: var(--text-secondary);
}
.dark .ledger-manage-card__action-btn {
background-color: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.05);
}
.ledger-manage-card__action-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Edit Button */
.ledger-manage-card__action-btn--edit:hover:not(:disabled) {
background-color: rgba(var(--accent-rgb), 0.1);
color: var(--accent-primary);
border-color: transparent;
transform: translateY(-2px);
}
/* Delete Button */
.ledger-manage-card__action-btn--delete:hover:not(:disabled) {
background-color: rgba(239, 68, 68, 0.1);
color: #ef4444;
border-color: transparent;
transform: translateY(-2px);
}
/* Restore Button */
.ledger-manage-card__action-btn--restore:hover:not(:disabled) {
background-color: rgba(16, 185, 129, 0.1);
color: #10b981;
border-color: transparent;
transform: translateY(-2px);
}
/* Modal Backdrop */
.ledger-manage-page__modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(8px);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 1rem;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.ledger-manage-page__modal-content {
background: var(--glass-panel-bg);
/* Use system panel bg */
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
border: 1px solid var(--glass-border);
border-radius: 24px;
max-width: 500px;
/* Bit narrower for better proportion */
width: 100%;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
animation: slideUp 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.dark .ledger-manage-page__modal-content {
background: rgba(30, 32, 45, 0.95);
/* More solid/dark in dark mode */
}
@keyframes slideUp {
from {
transform: translateY(20px) scale(0.95);
opacity: 0;
}
to {
transform: translateY(0) scale(1);
opacity: 1;
}
}
/* Loading & Empty States */
.ledger-manage-page__loading,
.ledger-manage-page__empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 4rem 1rem;
color: #6b7280;
padding: 6rem 1rem;
color: var(--text-tertiary);
text-align: center;
}
.ledger-manage-page__loading p {
.ledger-manage-page__loading p,
.ledger-manage-page__empty p {
margin-top: 1rem;
font-size: 1rem;
font-weight: 500;
}
.ledger-manage-page__spinner {
animation: spin 1s linear infinite;
color: var(--accent-primary);
}
@keyframes spin {
@@ -126,219 +414,17 @@
}
}
/* Empty State */
.ledger-manage-page__empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 4rem 1rem;
color: #9ca3af;
}
.ledger-manage-page__empty p {
margin-top: 1rem;
font-size: 1rem;
}
/* Ledger List */
.ledger-manage-page__list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 1.5rem;
}
/* Ledger Card */
.ledger-manage-card {
display: flex;
flex-direction: column;
background: var(--glass-panel-bg);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--glass-border);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-sm);
overflow: hidden;
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.ledger-manage-card:hover {
transform: translateY(-6px);
box-shadow: var(--shadow-xl);
border-color: rgba(var(--accent-rgb), 0.3);
}
/* Card Cover */
.ledger-manage-card__cover {
height: 160px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
.ledger-manage-card__cover-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.ledger-manage-card__cover-placeholder {
color: rgba(255, 255, 255, 0.8);
}
/* Card Info */
.ledger-manage-card__info {
padding: 1rem;
flex: 1;
}
.ledger-manage-card__name {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 1.125rem;
font-weight: 600;
color: #111827;
margin-bottom: 0.5rem;
}
.ledger-manage-card__default-badge {
display: inline-flex;
align-items: center;
padding: 0.125rem 0.5rem;
background-color: #dbeafe;
color: #1e40af;
font-size: 0.75rem;
font-weight: 500;
border-radius: 9999px;
}
.ledger-manage-card__meta {
display: flex;
flex-direction: column;
gap: 0.25rem;
font-size: 0.875rem;
color: #6b7280;
}
.ledger-manage-card__theme {
font-weight: 500;
}
/* Card Actions */
.ledger-manage-card__actions {
display: flex;
gap: 0.5rem;
padding: 1rem;
border-top: 1px solid #e5e7eb;
}
.ledger-manage-card__action-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.625rem 1rem;
border: 1px solid #d1d5db;
border-radius: 0.5rem;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
background-color: white;
}
.ledger-manage-card__action-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.ledger-manage-card__action-btn--edit {
color: #3b82f6;
border-color: #3b82f6;
}
.ledger-manage-card__action-btn--edit:hover:not(:disabled) {
background-color: #eff6ff;
}
.ledger-manage-card__action-btn--delete {
color: #ef4444;
border-color: #ef4444;
}
.ledger-manage-card__action-btn--delete:hover:not(:disabled) {
background-color: #fef2f2;
}
.ledger-manage-card__action-btn--restore {
color: #10b981;
border-color: #10b981;
}
.ledger-manage-card__action-btn--restore:hover:not(:disabled) {
background-color: #f0fdf4;
}
/* Modal */
.ledger-manage-page__modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 1rem;
}
.ledger-manage-page__modal-content {
background-color: white;
border-radius: 0.75rem;
max-width: 600px;
width: 100%;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
/* Responsive Design */
/* Responsive */
@media (max-width: 768px) {
.ledger-manage-page {
padding: 1rem;
}
.ledger-manage-page__title {
font-size: 1.5rem;
}
.ledger-manage-page__list {
grid-template-columns: 1fr;
}
.ledger-manage-page__tab {
padding: 0.625rem 1rem;
font-size: 0.875rem;
}
}
@media (max-width: 480px) {
.ledger-manage-page {
padding: 0.75rem;
}
.ledger-manage-card__actions {
flex-direction: column;
}
.ledger-manage-card__action-btn {
width: 100%;
.ledger-manage-page__title {
font-size: 1.75rem;
}
}

View File

@@ -17,6 +17,10 @@ import {
restoreLedger,
canDeleteLedger,
} from '../../services/ledgerService';
// Dynamic import for createLedger to avoid circular dependency issues if any, though likely not needed here.
// But importing directly is safer.
import { createLedger } from '../../services/ledgerService';
import './LedgerManagePage.css';
type ViewMode = 'active' | 'deleted';
@@ -30,8 +34,12 @@ export const LedgerManagePage: React.FC = () => {
const [viewMode, setViewMode] = useState<ViewMode>('active');
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Edit & Create State
const [editingLedger, setEditingLedger] = useState<Ledger | null>(null);
const [showEditForm, setShowEditForm] = useState(false);
const [showCreateForm, setShowCreateForm] = useState(false);
const [actionLoading, setActionLoading] = useState<number | null>(null);
// Load ledgers on mount
@@ -62,6 +70,28 @@ export const LedgerManagePage: React.FC = () => {
setShowEditForm(true);
};
const handleCreate = () => {
setShowCreateForm(true);
};
const handleCreateSubmit = async (data: LedgerFormData) => {
try {
setActionLoading(-1); // Use -1 for create action
await createLedger({
name: data.name,
theme: data.theme,
coverImage: data.coverImage,
});
await loadLedgers();
setShowCreateForm(false);
} catch (err) {
setError(err instanceof Error ? err.message : '创建账本失败');
console.error('Failed to create ledger:', err);
} finally {
setActionLoading(null);
}
};
const handleEditSubmit = async (data: LedgerFormData) => {
if (!editingLedger) return;
@@ -83,8 +113,9 @@ export const LedgerManagePage: React.FC = () => {
}
};
const handleEditCancel = () => {
const handleModalClose = () => {
setShowEditForm(false);
setShowCreateForm(false);
setEditingLedger(null);
};
@@ -239,6 +270,16 @@ export const LedgerManagePage: React.FC = () => {
{/* Header */}
<div className="ledger-manage-page__header">
<h1 className="ledger-manage-page__title"></h1>
{viewMode === 'active' && (
<button
className="ledger-manage-page__create-btn"
onClick={handleCreate}
aria-label="新建账本"
>
<Icon icon="mdi:plus" width="20" />
<span></span>
</button>
)}
</div>
{/* View Mode Tabs */}
@@ -298,18 +339,18 @@ export const LedgerManagePage: React.FC = () => {
)}
</div>
{/* Edit Form Modal */}
{showEditForm && editingLedger && (
<div className="ledger-manage-page__modal" onClick={handleEditCancel}>
{/* Edit/Create Form Modal */}
{(showEditForm || showCreateForm) && (
<div className="ledger-manage-page__modal" onClick={handleModalClose}>
<div
className="ledger-manage-page__modal-content"
onClick={(e) => e.stopPropagation()}
>
<LedgerForm
ledger={editingLedger}
onSubmit={handleEditSubmit}
onCancel={handleEditCancel}
loading={actionLoading === editingLedger.id}
ledger={editingLedger || undefined} // undefined for create mode
onSubmit={showCreateForm ? handleCreateSubmit : handleEditSubmit}
onCancel={handleModalClose}
loading={actionLoading === (editingLedger?.id || -1)}
/>
</div>
</div>