From 1e478c2eaaf7003384ddd0a34abcbadef9ea5dee Mon Sep 17 00:00:00 2001 From: Developer Date: Wed, 18 Mar 2026 18:42:31 +0800 Subject: [PATCH] =?UTF-8?q?Revert=20"feat:=20=E7=A7=BB=E5=8A=A8=E7=AB=AFUI?= =?UTF-8?q?=E7=B2=BE=E8=87=B4=E5=8C=96=E4=BC=98=E5=8C=96"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 349760b60c2dfd1d476a2fb65bc265d0e92ed370. --- frontend/components/EmptyState.js | 27 --- frontend/components/PageTransition.js | 41 ---- frontend/components/SwipeableItem.js | 93 ---------- frontend/pages/_app.js | 5 +- frontend/pages/index.js | 82 ++------ frontend/styles/globals.css | 258 +++----------------------- 6 files changed, 40 insertions(+), 466 deletions(-) delete mode 100644 frontend/components/EmptyState.js delete mode 100644 frontend/components/PageTransition.js delete mode 100644 frontend/components/SwipeableItem.js diff --git a/frontend/components/EmptyState.js b/frontend/components/EmptyState.js deleted file mode 100644 index 0153da4..0000000 --- a/frontend/components/EmptyState.js +++ /dev/null @@ -1,27 +0,0 @@ -import { Icon } from './icons'; - -export default function EmptyState({ - icon = 'info', - title = '暂无数据', - description = '这里还没有内容', - action, - actionLabel, - onAction, -}) { - return ( -
-
-
- -
-
-

{title}

- {description &&

{description}

} - {action && ( - - )} -
- ); -} diff --git a/frontend/components/PageTransition.js b/frontend/components/PageTransition.js deleted file mode 100644 index e5daa37..0000000 --- a/frontend/components/PageTransition.js +++ /dev/null @@ -1,41 +0,0 @@ -import { useEffect, useState } from 'react'; -import { useRouter } from 'next/router'; - -export default function PageTransition({ children }) { - const router = useRouter(); - const [isAnimating, setIsAnimating] = useState(false); - const [displayChildren, setDisplayChildren] = useState(children); - - useEffect(() => { - const handleRouteChangeStart = () => { - setIsAnimating(true); - }; - - const handleRouteChangeComplete = () => { - setDisplayChildren(children); - setTimeout(() => { - setIsAnimating(false); - }, 50); - }; - - router.events.on('routeChangeStart', handleRouteChangeStart); - router.events.on('routeChangeComplete', handleRouteChangeComplete); - - return () => { - router.events.off('routeChangeStart', handleRouteChangeStart); - router.events.off('routeChangeComplete', handleRouteChangeComplete); - }; - }, [router, children]); - - useEffect(() => { - if (!isAnimating) { - setDisplayChildren(children); - } - }, [children, isAnimating]); - - return ( -
- {displayChildren} -
- ); -} diff --git a/frontend/components/SwipeableItem.js b/frontend/components/SwipeableItem.js deleted file mode 100644 index eed4897..0000000 --- a/frontend/components/SwipeableItem.js +++ /dev/null @@ -1,93 +0,0 @@ -import { useState, useRef, useCallback } from 'react'; - -export default function SwipeableItem({ - children, - onSwipeLeft, - renderRightActions, - swipeThreshold = 80, -}) { - const [isSwiping, setIsSwiping] = useState(false); - const [translateX, setTranslateX] = useState(0); - const [isRevealed, setIsRevealed] = useState(false); - const startXRef = useRef(0); - const currentXRef = useRef(0); - const itemRef = useRef(null); - - const handleTouchStart = useCallback((e) => { - startXRef.current = e.touches[0].clientX; - currentXRef.current = startXRef.current; - setIsSwiping(true); - }, []); - - const handleTouchMove = useCallback((e) => { - if (!isSwiping) return; - - currentXRef.current = e.touches[0].clientX; - const diff = currentXRef.current - startXRef.current; - - // Only allow swiping left (negative diff) - if (diff < 0) { - // Limit the swipe distance - const newTranslateX = Math.max(diff, -swipeThreshold - 20); - setTranslateX(newTranslateX); - } else if (isRevealed) { - // If already revealed, allow swiping right to close - const newTranslateX = Math.min(0, -swipeThreshold + diff); - setTranslateX(newTranslateX); - } - }, [isSwiping, isRevealed, swipeThreshold]); - - const handleTouchEnd = useCallback(() => { - setIsSwiping(false); - const diff = currentXRef.current - startXRef.current; - - if (diff < -swipeThreshold / 2 || (isRevealed && diff > -swipeThreshold / 2)) { - // Reveal actions - setTranslateX(-swipeThreshold); - setIsRevealed(true); - } else { - // Snap back - setTranslateX(0); - setIsRevealed(false); - } - }, [isRevealed, swipeThreshold]); - - const handleClose = useCallback(() => { - setTranslateX(0); - setIsRevealed(false); - }, []); - - const handleActionClick = useCallback((callback) => { - callback?.(); - handleClose(); - }, [handleClose]); - - return ( -
-
- {children} -
- {renderRightActions && ( -
- {renderRightActions(handleActionClick)} -
- )} -
- ); -} diff --git a/frontend/pages/_app.js b/frontend/pages/_app.js index 9046097..798f429 100644 --- a/frontend/pages/_app.js +++ b/frontend/pages/_app.js @@ -2,7 +2,6 @@ import { SWRConfig } from 'swr'; import ErrorBoundary from '../components/ErrorBoundary'; import { ToastProvider } from '../components/ToastProvider'; import { ConfirmProvider } from '../components/ConfirmProvider'; -import PageTransition from '../components/PageTransition'; import { swrConfig } from '../lib/swr-config'; import '../styles/globals.css'; @@ -12,9 +11,7 @@ export default function App({ Component, pageProps }) { - - - + diff --git a/frontend/pages/index.js b/frontend/pages/index.js index 6e1de03..8d4da8f 100644 --- a/frontend/pages/index.js +++ b/frontend/pages/index.js @@ -4,12 +4,10 @@ import { isLoggedIn, getRecords, getStats, createRecord, updateRecord, deleteRec import AppShell from '../components/AppShell'; import { Icon } from '../components/icons'; import RecordModal from '../components/RecordModal'; -import SwipeableItem from '../components/SwipeableItem'; -import EmptyState from '../components/EmptyState'; import { useToast } from '../components/ToastProvider'; import { useConfirm } from '../components/ConfirmProvider'; import { useCategories } from '../hooks/useCategories'; -import { getMonthStr, formatMoney, formatMonth, parseDateTime } from '../lib/utils'; +import { getMonthStr, formatMoney, formatMonth, parseDateTime, getDatePart } from '../lib/utils'; export default function Home() { const router = useRouter(); @@ -193,23 +191,6 @@ export default function Home() { } }; - const handleQuickDelete = async (record) => { - const shouldDelete = await confirm({ - title: '删除这条记录?', - description: `${record.category} ${record.type === 'income' ? '+' : '-'}${formatMoney(record.amount)}`, - confirmText: '删除', - }); - if (!shouldDelete) return; - - try { - await deleteRecord(record.id); - setRecords(prev => prev.filter(r => r.id !== record.id)); - showToast('记录已删除', { tone: 'success' }); - } catch (err) { - showToast(err.message, { tone: 'error' }); - } - }; - const groupedRecords = useMemo(() => { return records.reduce((groups, record) => { const date = getDatePart(record.date); @@ -305,14 +286,10 @@ export default function Home() { {loading ? (
加载中...
) : records.length === 0 ? ( - +
+
+

暂无记录,点击上方按钮添加。

+
) : (
{sortedDates.map((date) => ( @@ -324,44 +301,19 @@ export default function Home() { {groupedRecords[date].map((record) => { const icon = categoryIconMap[`${record.type}:${record.category}`] || '📝'; return ( - openEditModal(record)} - renderRightActions={(handleAction) => ( - <> - - - - )} - > - - +
+
+ {record.type === 'income' ? '+' : '-'} + {formatMoney(record.amount)} +
+ ); })} diff --git a/frontend/styles/globals.css b/frontend/styles/globals.css index 6f8a6eb..7926c48 100644 --- a/frontend/styles/globals.css +++ b/frontend/styles/globals.css @@ -564,77 +564,24 @@ textarea:focus-visible { text-align: left; border: 1px solid rgba(148, 163, 184, 0.12); background: rgba(255, 255, 255, 0.82); - box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.45), 0 2px 8px rgba(15, 23, 42, 0.04); + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.45); cursor: pointer; transition: transform var(--transition), box-shadow var(--transition), border-color var(--transition), background var(--transition); } .stat-card:hover { - transform: translateY(-2px); - box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08); -} - -.stat-card:active { - transform: translateY(0) scale(0.98); + transform: translateY(-1px); + box-shadow: var(--shadow-xs); } .stat-card.active { border-color: rgba(37, 99, 235, 0.3); background: rgba(240, 246, 255, 0.96); - box-shadow: 0 10px 28px rgba(37, 99, 235, 0.12); -} - -/* Income stat card with gradient */ -.stat-card:has(.income) { - background: linear-gradient(135deg, rgba(5, 150, 105, 0.08) 0%, rgba(5, 150, 105, 0.02) 100%); - border-color: rgba(5, 150, 105, 0.15); -} - -.stat-card:has(.income):hover { - background: linear-gradient(135deg, rgba(5, 150, 105, 0.12) 0%, rgba(5, 150, 105, 0.04) 100%); - border-color: rgba(5, 150, 105, 0.22); - box-shadow: 0 8px 24px rgba(5, 150, 105, 0.1); -} - -.stat-card:has(.income).active { - background: linear-gradient(135deg, rgba(5, 150, 105, 0.16) 0%, rgba(5, 150, 105, 0.06) 100%); - border-color: rgba(5, 150, 105, 0.3); - box-shadow: 0 10px 28px rgba(5, 150, 105, 0.14); -} - -/* Expense stat card with gradient */ -.stat-card:has(.expense) { - background: linear-gradient(135deg, rgba(220, 38, 38, 0.08) 0%, rgba(220, 38, 38, 0.02) 100%); - border-color: rgba(220, 38, 38, 0.15); -} - -.stat-card:has(.expense):hover { - background: linear-gradient(135deg, rgba(220, 38, 38, 0.12) 0%, rgba(220, 38, 38, 0.04) 100%); - border-color: rgba(220, 38, 38, 0.22); - box-shadow: 0 8px 24px rgba(220, 38, 38, 0.1); -} - -.stat-card:has(.expense).active { - background: linear-gradient(135deg, rgba(220, 38, 38, 0.16) 0%, rgba(220, 38, 38, 0.06) 100%); - border-color: rgba(220, 38, 38, 0.3); - box-shadow: 0 10px 28px rgba(220, 38, 38, 0.14); + box-shadow: 0 10px 24px rgba(37, 99, 235, 0.08); } .stat-card--balance { - background: linear-gradient(135deg, rgba(37, 99, 235, 0.08) 0%, rgba(37, 99, 235, 0.02) 100%); - border-color: rgba(37, 99, 235, 0.15); -} - -.stat-card--balance:hover { - background: linear-gradient(135deg, rgba(37, 99, 235, 0.12) 0%, rgba(37, 99, 235, 0.04) 100%); - border-color: rgba(37, 99, 235, 0.22); - box-shadow: 0 8px 24px rgba(37, 99, 235, 0.1); -} - -.stat-card--balance.active { - background: linear-gradient(135deg, rgba(37, 99, 235, 0.16) 0%, rgba(37, 99, 235, 0.06) 100%); - border-color: rgba(37, 99, 235, 0.3); - box-shadow: 0 10px 28px rgba(37, 99, 235, 0.14); + background: rgba(255, 255, 255, 0.82); } .label { @@ -1870,53 +1817,31 @@ textarea:focus-visible { .bottom-nav__inner { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: 4px; - padding: 6px 8px calc(6px + env(safe-area-inset-bottom)); - border-radius: 20px 20px 0 0; - background: rgba(255, 255, 255, 0.88); - border: 1px solid rgba(255, 255, 255, 0.7); - box-shadow: 0 -8px 24px rgba(15, 23, 42, 0.05); + gap: 6px; + padding: 10px 10px 11px; + border-radius: 22px 22px 0 0; + background: rgba(255, 255, 255, 0.84); + border: 1px solid rgba(255, 255, 255, 0.66); + box-shadow: 0 -12px 30px rgba(15, 23, 42, 0.06); backdrop-filter: blur(24px); } .bottom-nav__item { - min-height: 48px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 3px; - padding: 5px 4px; - border-radius: 14px; + min-height: 54px; + display: grid; + justify-items: center; + align-content: center; + gap: 4px; + padding: 7px 4px; + border-radius: 16px; color: var(--text-tertiary); cursor: pointer; transition: background var(--transition), color var(--transition), transform var(--transition); - position: relative; - overflow: hidden; -} - -.bottom-nav__item::before { - content: ''; - position: absolute; - top: 50%; - left: 50%; - width: 0; - height: 0; - background: rgba(37, 99, 235, 0.1); - border-radius: 50%; - transform: translate(-50%, -50%); - transition: width 0.3s ease, height 0.3s ease; -} - -.bottom-nav__item:active::before { - width: 100%; - height: 100%; } .bottom-nav__item span { - font-size: 0.7rem; - font-weight: 600; - letter-spacing: 0.02em; + font-size: 0.74rem; + font-weight: 700; } .bottom-nav__item.is-active { @@ -1924,18 +1849,6 @@ textarea:focus-visible { color: var(--primary); } -.bottom-nav__item.is-active::before { - display: none; -} - -.bottom-nav__item .icon { - transition: transform 0.2s ease; -} - -.bottom-nav__item:active .icon { - transform: scale(0.9); -} - .checkbox-label { display: inline-flex; align-items: center; @@ -2278,133 +2191,6 @@ textarea:focus-visible { to { transform: rotate(360deg); } } -/* Swipeable Item */ -.swipeable-item { - position: relative; - overflow: hidden; -} - -.swipeable-content { - position: relative; - z-index: 1; - background: var(--surface); - cursor: grab; -} - -.swipeable-content.is-swiping { - cursor: grabbing; -} - -.swipeable-actions { - position: absolute; - top: 0; - right: 0; - bottom: 0; - display: flex; - align-items: center; - justify-content: flex-end; - gap: 8px; - padding: 0 12px; - background: transparent; - z-index: 0; -} - -.swipeable-action { - width: 56px; - height: 56px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 14px; - cursor: pointer; - transition: transform 0.15s ease, box-shadow 0.15s ease; -} - -.swipeable-action:active { - transform: scale(0.92); -} - -.swipeable-action--edit { - background: var(--primary-soft); - color: var(--primary); -} - -.swipeable-action--delete { - background: var(--expense-soft); - color: var(--expense); -} - -/* Empty State Enhanced */ -.empty-state-enhanced { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 40px 24px; - text-align: center; -} - -.empty-state-illustration { - position: relative; - margin-bottom: 20px; -} - -.empty-state-bg { - width: 80px; - height: 80px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 24px; - background: linear-gradient(135deg, rgba(37, 99, 235, 0.1) 0%, rgba(37, 99, 235, 0.04) 100%); - color: var(--primary); - position: relative; -} - -.empty-state-bg::before { - content: ''; - position: absolute; - inset: -8px; - background: radial-gradient(circle, rgba(37, 99, 235, 0.08) 0%, transparent 70%); - border-radius: 50%; - z-index: -1; -} - -.empty-state-title { - font-size: 1.1rem; - font-weight: 700; - color: var(--text); - margin-bottom: 8px; -} - -.empty-state-description { - font-size: 0.9rem; - color: var(--text-secondary); - line-height: 1.5; - margin-bottom: 20px; - max-width: 240px; -} - -.empty-state-action { - min-width: 140px; - min-height: 44px; -} - -.empty-state-action .icon { - margin-right: 6px; -} -.page-transition { - opacity: 1; - transform: translateY(0); - transition: opacity 250ms ease-out, transform 250ms ease-out; -} - -.page-transition--entering { - opacity: 0; - transform: translateY(12px); -} - -.page-transition--active { - opacity: 1; - transform: translateY(0); +.is-spinning { + animation: spin 1s linear infinite; }