'use client'; import { useEffect, useRef, useState, useMemo, useLayoutEffect, useCallback } from 'react'; import { motion, AnimatePresence, Reorder } from 'framer-motion'; import { createAvatar } from '@dicebear/core'; import { glass } from '@dicebear/collection'; import Announcement from "./components/Announcement"; import zhifubaoImg from "./assets/zhifubao.jpg"; import weixinImg from "./assets/weixin.jpg"; import githubImg from "./assets/github.svg"; import { supabase } from './lib/supabase'; import packageJson from '../package.json'; import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; import timezone from 'dayjs/plugin/timezone'; dayjs.extend(utc); dayjs.extend(timezone); dayjs.tz.setDefault('Asia/Shanghai'); const TZ = 'Asia/Shanghai'; const nowInTz = () => dayjs().tz(TZ); const toTz = (input) => (input ? dayjs.tz(input, TZ) : nowInTz()); const formatDate = (input) => toTz(input).format('YYYY-MM-DD'); // 全局 JSONP/Script 加载辅助函数 const loadScript = (url) => { return new Promise((resolve, reject) => { if (typeof document === 'undefined') return resolve(); const script = document.createElement('script'); script.src = url; script.async = true; script.onload = () => { if (document.body.contains(script)) document.body.removeChild(script); resolve(); }; script.onerror = () => { if (document.body.contains(script)) document.body.removeChild(script); reject(new Error('加载失败')); }; document.body.appendChild(script); }); }; // 获取指定日期的基金净值 const fetchFundNetValue = async (code, date) => { // 使用东方财富 F10 接口获取历史净值 HTML const url = `https://fundf10.eastmoney.com/F10DataApi.aspx?type=lsjz&code=${code}&page=1&per=1&sdate=${date}&edate=${date}`; try { await loadScript(url); if (window.apidata && window.apidata.content) { const content = window.apidata.content; if (content.includes('暂无数据')) return null; // 解析 HTML 表格 // 格式: 日期单位净值... const rows = content.split(''); for (const row of rows) { if (row.includes(`${date}`)) { // 找到对应日期的行,提取单元格 const cells = row.match(/]*>(.*?)<\/td>/g); if (cells && cells.length >= 2) { // 第二列是单位净值 (cells[1]) const valStr = cells[1].replace(/<[^>]+>/g, ''); const val = parseFloat(valStr); return isNaN(val) ? null : val; } } } } return null; } catch (e) { console.error('获取净值失败', e); return null; } }; const fetchSmartFundNetValue = async (code, startDate) => { const today = nowInTz().startOf('day'); let current = toTz(startDate).startOf('day'); for (let i = 0; i < 30; i++) { if (current.isAfter(today)) break; const dateStr = current.format('YYYY-MM-DD'); const val = await fetchFundNetValue(code, dateStr); if (val !== null) { return { date: dateStr, value: val }; } current = current.add(1, 'day'); } return null; }; function PlusIcon(props) { return ( ); } function UpdateIcon(props) { return ( ); } function TrashIcon(props) { return ( ); } function SettingsIcon(props) { return ( ); } function CloudIcon(props) { return ( ); } function RefreshIcon(props) { return ( ); } function ChevronIcon(props) { return ( ); } function SortIcon(props) { return ( ); } function UserIcon(props) { return ( ); } function LogoutIcon(props) { return ( ); } function LoginIcon(props) { return ( ); } function MailIcon(props) { return ( ); } function GridIcon(props) { return ( ); } function CloseIcon(props) { return ( ); } function ExitIcon(props) { return ( ); } function ListIcon(props) { return ( ); } function DragIcon(props) { return ( ); } function FolderPlusIcon(props) { return ( ); } function StarIcon({ filled, ...props }) { return ( ); } function CalendarIcon(props) { return ( ); } function DatePicker({ value, onChange }) { const [isOpen, setIsOpen] = useState(false); const [currentMonth, setCurrentMonth] = useState(() => value ? toTz(value) : nowInTz()); // 点击外部关闭 useEffect(() => { const close = () => setIsOpen(false); if (isOpen) window.addEventListener('click', close); return () => window.removeEventListener('click', close); }, [isOpen]); const year = currentMonth.year(); const month = currentMonth.month(); const handlePrevMonth = (e) => { e.stopPropagation(); setCurrentMonth(currentMonth.subtract(1, 'month').startOf('month')); }; const handleNextMonth = (e) => { e.stopPropagation(); setCurrentMonth(currentMonth.add(1, 'month').startOf('month')); }; const handleSelect = (e, day) => { e.stopPropagation(); const dateStr = formatDate(`${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`); const today = nowInTz().startOf('day'); const selectedDate = toTz(dateStr).startOf('day'); if (selectedDate.isAfter(today)) return; onChange(dateStr); setIsOpen(false); }; // 生成日历数据 const daysInMonth = currentMonth.daysInMonth(); const firstDayOfWeek = currentMonth.startOf('month').day(); const days = []; for (let i = 0; i < firstDayOfWeek; i++) days.push(null); for (let i = 1; i <= daysInMonth; i++) days.push(i); return (
e.stopPropagation()}>
setIsOpen(!isOpen)} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 12px', height: '40px', background: 'rgba(0,0,0,0.2)', borderRadius: '8px', cursor: 'pointer', border: '1px solid transparent', transition: 'all 0.2s' }} > {value || '选择日期'}
{isOpen && (
{year}年 {month + 1}月
{['日', '一', '二', '三', '四', '五', '六'].map(d => (
{d}
))} {days.map((d, i) => { if (!d) return
; const dateStr = formatDate(`${year}-${String(month + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`); const isSelected = value === dateStr; const today = nowInTz().startOf('day'); const current = toTz(dateStr).startOf('day'); const isToday = current.isSame(today); const isFuture = current.isAfter(today); return (
!isFuture && handleSelect(e, d)} style={{ height: 28, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '13px', borderRadius: '6px', cursor: isFuture ? 'not-allowed' : 'pointer', background: isSelected ? 'var(--primary)' : isToday ? 'rgba(255,255,255,0.1)' : 'transparent', color: isFuture ? 'var(--muted)' : isSelected ? '#000' : 'var(--text)', fontWeight: isSelected || isToday ? 600 : 400, opacity: isFuture ? 0.3 : 1 }} onMouseEnter={(e) => { if (!isSelected && !isFuture) e.currentTarget.style.background = 'rgba(255,255,255,0.1)'; }} onMouseLeave={(e) => { if (!isSelected && !isFuture) e.currentTarget.style.background = isToday ? 'rgba(255,255,255,0.1)' : 'transparent'; }} > {d}
); })}
)}
); } function DonateTabs() { const [method, setMethod] = useState('wechat'); // alipay, wechat return (
{method === 'alipay' ? ( 支付宝收款码 ) : ( 微信收款码 )}
); } function MinusIcon(props) { return ( ); } function NumericInput({ value, onChange, step = 1, min = 0, placeholder }) { const decimals = String(step).includes('.') ? String(step).split('.')[1].length : 0; const fmt = (n) => Number(n).toFixed(decimals); const inc = () => { const v = parseFloat(value); const base = isNaN(v) ? 0 : v; const next = base + step; onChange(fmt(next)); }; const dec = () => { const v = parseFloat(value); const base = isNaN(v) ? 0 : v; const next = Math.max(min, base - step); onChange(fmt(next)); }; return (
onChange(e.target.value)} placeholder={placeholder} style={{ width: '100%', paddingRight: 56 }} />
); } function Stat({ label, value, delta }) { const dir = delta > 0 ? 'up' : delta < 0 ? 'down' : ''; return (
{label} {value}
); } function FeedbackModal({ onClose, user }) { const [submitting, setSubmitting] = useState(false); const [succeeded, setSucceeded] = useState(false); const [error, setError] = useState(""); const onSubmit = async (e) => { e.preventDefault(); setSubmitting(true); setError(""); const formData = new FormData(e.target); const nickname = formData.get("nickname")?.trim(); if (!nickname) { formData.set("nickname", "匿名"); } // Web3Forms Access Key formData.append("access_key", "c390fbb1-77e0-4aab-a939-caa75edc7319"); formData.append("subject", "基估宝 - 用户反馈"); try { const response = await fetch("https://api.web3forms.com/submit", { method: "POST", body: formData }); const data = await response.json(); if (data.success) { setSucceeded(true); } else { setError(data.message || "提交失败,请稍后再试"); } } catch (err) { setError("网络错误,请检查您的连接"); } finally { setSubmitting(false); } }; return ( e.stopPropagation()} >
意见反馈
{succeeded ? (
🎉

感谢您的反馈!

我们已收到您的建议,会尽快查看。

) : (