diff --git a/app/components/Common.jsx b/app/components/Common.jsx index ae17dca..53dd20b 100644 --- a/app/components/Common.jsx +++ b/app/components/Common.jsx @@ -27,7 +27,7 @@ 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 }) { +export function DatePicker({ value, onChange, position = 'bottom' }) { const [isOpen, setIsOpen] = useState(false); const [currentMonth, setCurrentMonth] = useState(() => value ? toTz(value) : nowInTz()); @@ -83,16 +83,15 @@ export function DatePicker({ value, onChange }) { {isOpen && ( + {holding?.firstPurchaseDate && !masked && (() => { + const today = dayjs.tz(todayStr, TZ); + const purchaseDate = dayjs.tz(holding.firstPurchaseDate, TZ); + if (!purchaseDate.isValid()) return null; + const days = today.diff(purchaseDate, 'day'); + return ( +
+ 持有天数 + + {days}天 + +
+ ); + })()}
当日收益 { if (!holding) return ''; - return `${holding.id ?? ''}|${holding.share ?? ''}|${holding.cost ?? ''}`; + return `${holding.id ?? ''}|${holding.share ?? ''}|${holding.cost ?? ''}|${holding.firstPurchaseDate ?? ''}`; }, [holding]); useEffect(() => { @@ -33,6 +47,14 @@ export default function HoldingEditModal({ fund, holding, onClose, onSave, onOpe const c = holding.cost || 0; setShare(String(s)); setCost(String(c)); + setFirstPurchaseDate(holding.firstPurchaseDate || ''); + + if (holding.firstPurchaseDate) { + const days = dayjs.tz(undefined, TZ).diff(dayjs.tz(holding.firstPurchaseDate, TZ), 'day'); + setHoldingDaysInput(days > 0 ? String(days) : ''); + } else { + setHoldingDaysInput(''); + } const price = dwjzRef.current; if (price > 0) { @@ -42,7 +64,6 @@ export default function HoldingEditModal({ fund, holding, onClose, onSave, onOpe setProfit(p.toFixed(2)); } } - // 只在“切换持仓/初次打开”时初始化,避免净值刷新覆盖用户输入 // eslint-disable-next-line react-hooks/exhaustive-deps }, [holdingSig]); @@ -74,6 +95,41 @@ export default function HoldingEditModal({ fund, holding, onClose, onSave, onOpe } }; + const handleDateModeToggle = () => { + const newMode = dateMode === 'date' ? 'days' : 'date'; + setDateMode(newMode); + + if (newMode === 'days' && firstPurchaseDate) { + const days = dayjs.tz(undefined, TZ).diff(dayjs.tz(firstPurchaseDate, TZ), 'day'); + setHoldingDaysInput(days > 0 ? String(days) : ''); + } else if (newMode === 'date' && holdingDaysInput) { + const days = parseInt(holdingDaysInput, 10); + if (Number.isFinite(days) && days >= 0) { + const date = dayjs.tz(undefined, TZ).subtract(days, 'day').format('YYYY-MM-DD'); + setFirstPurchaseDate(date); + } + } + }; + + const handleHoldingDaysChange = (value) => { + setHoldingDaysInput(value); + const days = parseInt(value, 10); + if (Number.isFinite(days) && days >= 0) { + const date = dayjs.tz(undefined, TZ).subtract(days, 'day').format('YYYY-MM-DD'); + setFirstPurchaseDate(date); + } + }; + + const handleFirstPurchaseDateChange = (value) => { + setFirstPurchaseDate(value); + if (value) { + const days = dayjs.tz(undefined, TZ).diff(dayjs.tz(value, TZ), 'day'); + setHoldingDaysInput(days > 0 ? String(days) : ''); + } else { + setHoldingDaysInput(''); + } + }; + const handleSubmit = (e) => { e.preventDefault(); @@ -94,9 +150,12 @@ export default function HoldingEditModal({ fund, holding, onClose, onSave, onOpe finalCost = finalShare > 0 ? principal / finalShare : 0; } + const trimmedDate = firstPurchaseDate ? firstPurchaseDate.trim() : ''; + onSave({ share: finalShare, - cost: finalCost + cost: finalCost, + ...(trimmedDate && { firstPurchaseDate: trimmedDate }) }); onClose(); }; @@ -255,6 +314,49 @@ export default function HoldingEditModal({ fund, holding, onClose, onSave, onOpe )} +
+
+ + {dateMode === 'date' ? '首次买入日期' : '持有天数'} + + +
+ {dateMode === 'date' ? ( + + ) : ( + handleHoldingDaysChange(e.target.value)} + placeholder="请输入持有天数" + style={{ width: '100%' }} + /> + )} +
+