'use client'; import { useEffect, useState } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import Image from 'next/image'; import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; import timezone from 'dayjs/plugin/timezone'; import zhifubaoImg from "../assets/zhifubao.jpg"; import weixinImg from "../assets/weixin.jpg"; import { CalendarIcon, MinusIcon, PlusIcon } from './Icons'; dayjs.extend(utc); dayjs.extend(timezone); const DEFAULT_TZ = 'Asia/Shanghai'; const getBrowserTimeZone = () => { if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) { const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; return tz || DEFAULT_TZ; } return DEFAULT_TZ; }; const TZ = getBrowserTimeZone(); dayjs.tz.setDefault(TZ); const nowInTz = () => dayjs().tz(TZ); const toTz = (input) => (input ? dayjs.tz(input, TZ) : nowInTz()); const formatDate = (input) => toTz(input).format('YYYY-MM-DD'); export 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}
); })}
)}
); } export function DonateTabs() { const [method, setMethod] = useState('wechat'); return (
{method === 'alipay' ? ( 支付宝收款码 ) : ( 微信收款码 )}
); } export 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 }} />
); } export function Stat({ label, value, delta }) { const dir = delta > 0 ? 'up' : delta < 0 ? 'down' : ''; return (
{label} {value}
); }