From 26e995ec958dfb1d7253487522854d4117b5ea52 Mon Sep 17 00:00:00 2001 From: hzm <934585316@qq.com> Date: Sun, 1 Feb 2026 17:34:45 +0800 Subject: [PATCH] =?UTF-8?q?add=EF=BC=9A=E5=A2=9E=E5=8A=A0=E6=8E=92?= =?UTF-8?q?=E5=BA=8F=E5=92=8C=E5=88=97=E8=A1=A8=E6=A8=A1=E5=BC=8F=E5=88=87?= =?UTF-8?q?=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/globals.css | 132 ++++++++++++++- app/page.jsx | 400 +++++++++++++++++++++++++++++++++++----------- next.config.js | 3 - package-lock.json | 43 +++++ package.json | 1 + 5 files changed, 475 insertions(+), 104 deletions(-) diff --git a/app/globals.css b/app/globals.css index 42aeaba..411d007 100644 --- a/app/globals.css +++ b/app/globals.css @@ -367,19 +367,143 @@ body { backdrop-filter: blur(8px); } +.card.list-mode { + padding: 12px 16px; + position: relative; +} + +.table-container { + padding: 0; + overflow: hidden; + grid-column: span 12; +} + +.table-row-wrapper { + width: 100%; +} + +.table-row { + display: grid; + grid-template-columns: 2fr 1fr 1fr 1.5fr 60px; + align-items: center; + gap: 12px; + padding: 12px 24px !important; + border-bottom: 1px solid var(--border); + transition: background-color 0.2s ease; +} + +.table-row:hover { + background: rgba(255, 255, 255, 0.03); +} + +.table-row:last-child { + border-bottom: none; +} + +.table-header-row { + display: grid; + grid-template-columns: 2fr 1fr 1fr 1.5fr 60px; + gap: 12px; + padding: 16px 24px; + background: rgba(255, 255, 255, 0.05); + border-bottom: 1px solid var(--border); +} + +.table-header-cell { + font-size: 13px; + color: var(--text); + font-weight: 700; + letter-spacing: 0.5px; +} + +.table-cell { + display: flex; + align-items: center; +} + +.text-right { text-align: right; justify-content: flex-end; } +.text-center { text-align: center; justify-content: center; } + +.name-cell { + gap: 8px; + overflow: hidden; +} + +.title-text { + display: flex; + flex-direction: column; + gap: 2px; +} + +.name-text { + font-size: 14px; + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.code-text { + font-size: 11px; +} + +@media (max-width: 768px) { + .table-header-row { + display: none; + } + .table-row { + grid-template-columns: 1fr 80px 80px; + grid-template-areas: + "name value change" + "name time action"; + gap: 4px 12px; + padding: 12px !important; + } + .name-cell { grid-area: name; } + .value-cell { grid-area: value; } + .change-cell { grid-area: change; } + .time-cell { + grid-area: time; + justify-content: flex-end; + } + .action-cell { + grid-area: action; + justify-content: flex-end; + } + .table-cell.time-cell span { + font-size: 10px !important; + } +} + +.stat-compact .up { color: var(--danger); } +.stat-compact .down { color: var(--success); } + +.filter-bar { + transition: all 0.3s ease; +} + @media (max-width: 640px) { - .tabs { + .filter-bar { position: sticky; - top: 60px; /* Navbar height (12px*2 + 24px) */ + top: 60px; /* Navbar height */ z-index: 40; width: calc(100% + 32px); - justify-content: center; background: rgba(15, 23, 42, 0.9); - border-radius: 0; margin: 0 -16px 16px -16px; padding: 10px 16px; border-bottom: 1px solid var(--border); backdrop-filter: blur(16px); + display: flex; + flex-direction: column; + align-items: center !important; + } + .tabs { + width: 100%; + justify-content: center; + background: transparent; + border-radius: 0; + backdrop-filter: none; + padding: 0; } } diff --git a/app/page.jsx b/app/page.jsx index 6a746c8..3e541e2 100644 --- a/app/page.jsx +++ b/app/page.jsx @@ -1,6 +1,7 @@ 'use client'; import { useEffect, useRef, useState } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; function PlusIcon(props) { return ( @@ -49,6 +50,33 @@ function ChevronIcon(props) { ); } +function SortIcon(props) { + return ( + + + + ); +} + +function GridIcon(props) { + return ( + + + + + + + ); +} + +function ListIcon(props) { + return ( + + + + ); +} + function StarIcon({ filled, ...props }) { return ( @@ -94,6 +122,12 @@ export default function HomePage() { const [favorites, setFavorites] = useState(new Set()); const [currentTab, setCurrentTab] = useState('all'); + // 排序状态 + const [sortBy, setSortBy] = useState('default'); // default, name, yield, code + + // 视图模式 + const [viewMode, setViewMode] = useState('card'); // card, list + const toggleFavorite = (code) => { setFavorites(prev => { const next = new Set(prev); @@ -144,6 +178,11 @@ export default function HomePage() { if (Array.isArray(savedFavorites)) { setFavorites(new Set(savedFavorites)); } + // 加载视图模式 + const savedViewMode = localStorage.getItem('viewMode'); + if (savedViewMode === 'card' || savedViewMode === 'list') { + setViewMode(savedViewMode); + } } catch {} }, []); @@ -315,6 +354,12 @@ export default function HomePage() { } }; + const toggleViewMode = () => { + const nextMode = viewMode === 'card' ? 'list' : 'card'; + setViewMode(nextMode); + localStorage.setItem('viewMode', nextMode); + }; + const addFund = async (e) => { e.preventDefault(); setError(''); @@ -449,117 +494,278 @@ export default function HomePage() {
- {funds.length > 0 && favorites.size > 0 && ( -
- - + {funds.length > 0 && ( +
+ {favorites.size > 0 ? ( +
+ + +
+ ) :
} + +
+
+ + +
+ +
+ +
+ + + 排序 + +
+ {[ + { id: 'default', label: '默认' }, + { id: 'yield', label: '涨跌幅' }, + { id: 'name', label: '名称' }, + { id: 'code', label: '代码' } + ].map((s) => ( + + ))} +
+
+
)} {funds.length === 0 ? (
尚未添加基金
) : ( -
- {funds - .filter(f => currentTab === 'all' || favorites.has(f.code)) - .map((f) => ( -
-
-
-
- - {f.name} - #{f.code} -
-
-
- 估值时间 - {f.gztime || f.time || '-'} -
- -
-
-
- - - -
-
toggleCollapse(f.code)} - > -
-
- 前10重仓股票 - -
- 涨跌幅 / 占比 -
-
- {Array.isArray(f.holdings) && f.holdings.length ? ( -
- {f.holdings.map((h, idx) => ( -
- {h.name} -
- {typeof h.change === 'number' && ( - 0 ? 'up' : h.change < 0 ? 'down' : ''}`} style={{ marginRight: 8 }}> - {h.change > 0 ? '+' : ''}{h.change.toFixed(2)}% - - )} - {h.weight} + + + {viewMode === 'list' && ( +
+
基金名称
+
估值净值
+
涨跌幅
+
估值时间
+
操作
+
+ )} +
+ + {funds + .filter(f => currentTab === 'all' || favorites.has(f.code)) + .sort((a, b) => { + if (sortBy === 'yield') { + const valA = typeof a.estGszzl === 'number' ? a.estGszzl : (Number(a.gszzl) || 0); + const valB = typeof b.estGszzl === 'number' ? b.estGszzl : (Number(b.gszzl) || 0); + return valB - valA; + } + if (sortBy === 'name') return a.name.localeCompare(b.name, 'zh-CN'); + if (sortBy === 'code') return a.code.localeCompare(b.code); + return 0; // default order is the order in the array + }) + .map((f) => ( + +
+ {viewMode === 'list' ? ( + <> +
+ +
+ {f.name} + #{f.code} +
+
+
+ {f.estPricedCoverage > 0.05 ? f.estGsz.toFixed(4) : (f.gsz ?? '—')} +
+
+ 0.05 ? (f.estGszzl > 0 ? 'up' : f.estGszzl < 0 ? 'down' : '') : (Number(f.gszzl) > 0 ? 'up' : Number(f.gszzl) < 0 ? 'down' : '')} style={{ fontWeight: 700 }}> + {f.estPricedCoverage > 0.05 ? `${f.estGszzl > 0 ? '+' : ''}${f.estGszzl.toFixed(2)}%` : (typeof f.gszzl === 'number' ? `${f.gszzl > 0 ? '+' : ''}${f.gszzl.toFixed(2)}%` : f.gszzl ?? '—')} + +
+
+ {f.gztime || f.time || '-'} +
+
+ +
+ + ) : ( + <> +
+
+ +
+ {f.name} + #{f.code} +
+
+ +
+
+ 估值时间 + {f.gztime || f.time || '-'} +
+
- ))} + +
+ + 0.05 ? f.estGsz.toFixed(4) : (f.gsz ?? '—')} /> + 0.05 ? `${f.estGszzl > 0 ? '+' : ''}${f.estGszzl.toFixed(2)}%` : (typeof f.gszzl === 'number' ? `${f.gszzl > 0 ? '+' : ''}${f.gszzl.toFixed(2)}%` : f.gszzl ?? '—')} + delta={f.estPricedCoverage > 0.05 ? f.estGszzl : (Number(f.gszzl) || 0)} + /> +
+ {f.estPricedCoverage > 0.05 && ( +
+ 基于 {Math.round(f.estPricedCoverage * 100)}% 持仓估算 +
+ )} +
toggleCollapse(f.code)} + > +
+
+ 前10重仓股票 + +
+ 涨跌幅 / 占比 +
+
+ + {!collapsedCodes.has(f.code) && ( + + {Array.isArray(f.holdings) && f.holdings.length ? ( +
+ {f.holdings.map((h, idx) => ( +
+ {h.name} +
+ {typeof h.change === 'number' && ( + 0 ? 'up' : h.change < 0 ? 'down' : ''}`} style={{ marginRight: 8 }}> + {h.change > 0 ? '+' : ''}{h.change.toFixed(2)}% + + )} + {h.weight} +
+
+ ))} +
+ ) : ( +
暂无重仓数据
+ )} +
+ )} +
+ + )}
- ) : ( -
暂无重仓数据
- )} -
+
+ ))} +
- ))} -
+ + )}
-
数据源:实时估值与重仓直连东方财富,无需后端,部署即用
+
+

数据源:实时估值与重仓直连东方财富,无需后端,部署即用

+

注:估算数据与真实结算数据会有1%左右误差

+
{settingsOpen && (
setSettingsOpen(false)}> diff --git a/next.config.js b/next.config.js index 75c2297..91ef62f 100644 --- a/next.config.js +++ b/next.config.js @@ -1,9 +1,6 @@ /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, - experimental: { - appDir: true - } }; module.exports = nextConfig; diff --git a/package-lock.json b/package-lock.json index b85fc7e..7b7b970 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "real-time-fund", "version": "0.1.0", "dependencies": { + "framer-motion": "^12.29.2", "next": "14.2.5", "react": "18.3.1", "react-dom": "18.3.1" @@ -216,6 +217,33 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/framer-motion": { + "version": "12.29.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.29.2.tgz", + "integrity": "sha512-lSNRzBJk4wuIy0emYQ/nfZ7eWhqud2umPKw2QAQki6uKhZPKm2hRQHeQoHTG9MIvfobb+A/LbEWPJU794ZUKrg==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.29.2", + "motion-utils": "^12.29.2", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -240,6 +268,21 @@ "loose-envify": "cli.js" } }, + "node_modules/motion-dom": { + "version": "12.29.2", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.29.2.tgz", + "integrity": "sha512-/k+NuycVV8pykxyiTCoFzIVLA95Nb1BFIVvfSu9L50/6K6qNeAYtkxXILy/LRutt7AzaYDc2myj0wkCVVYAPPA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.29.2" + } + }, + "node_modules/motion-utils": { + "version": "12.29.2", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.29.2.tgz", + "integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==", + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", diff --git a/package.json b/package.json index 5bc5d36..a0b7911 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "start": "next start" }, "dependencies": { + "framer-motion": "^12.29.2", "next": "14.2.5", "react": "18.3.1", "react-dom": "18.3.1"