diff --git a/app/api/fund.js b/app/api/fund.js index 7b815b6..8798556 100644 --- a/app/api/fund.js +++ b/app/api/fund.js @@ -720,7 +720,7 @@ const snapshotPingzhongdataGlobals = (fundCode) => { }; }; -const jsonpLoadPingzhongdata = (fundCode, timeoutMs = 10000) => { +const jsonpLoadPingzhongdata = (fundCode, timeoutMs = 20000) => { return new Promise((resolve, reject) => { if (typeof document === 'undefined' || !document.body) { reject(new Error('无浏览器环境')); diff --git a/app/components/TradeModal.jsx b/app/components/TradeModal.jsx index a5059df..024d26b 100644 --- a/app/components/TradeModal.jsx +++ b/app/components/TradeModal.jsx @@ -6,7 +6,7 @@ import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; import timezone from 'dayjs/plugin/timezone'; import { isNumber } from 'lodash'; -import { fetchSmartFundNetValue } from '../api/fund'; +import { fetchFundPingzhongdata, fetchSmartFundNetValue } from '../api/fund'; import { DatePicker, NumericInput } from './Common'; import ConfirmModal from './ConfirmModal'; import { CloseIcon } from './Icons'; @@ -16,6 +16,7 @@ import { DialogTitle, } from '@/components/ui/dialog'; import PendingTradesModal from './PendingTradesModal'; +import { Spinner } from '@/components/ui/spinner'; dayjs.extend(utc); dayjs.extend(timezone); @@ -39,12 +40,65 @@ export default function TradeModal({ type, fund, holding, onClose, onConfirm, pe const [share, setShare] = useState(''); const [amount, setAmount] = useState(''); const [feeRate, setFeeRate] = useState('0'); + const [minBuyAmount, setMinBuyAmount] = useState(0); + const [loadingBuyMeta, setLoadingBuyMeta] = useState(false); + const [buyMetaError, setBuyMetaError] = useState(null); const [date, setDate] = useState(() => { return formatDate(); }); const [isAfter3pm, setIsAfter3pm] = useState(nowInTz().hour() >= 15); const [calcShare, setCalcShare] = useState(null); + const parseNumberish = (input) => { + if (input === null || typeof input === 'undefined') return null; + if (typeof input === 'number') return Number.isFinite(input) ? input : null; + const cleaned = String(input).replace(/[^\d.]/g, ''); + const n = parseFloat(cleaned); + return Number.isFinite(n) ? n : null; + }; + + useEffect(() => { + if (!isBuy || !fund?.code) return; + let cancelled = false; + + setLoadingBuyMeta(true); + setBuyMetaError(null); + + fetchFundPingzhongdata(fund.code) + .then((pz) => { + if (cancelled) return; + const rate = parseNumberish(pz?.fund_Rate); + const minsg = parseNumberish(pz?.fund_minsg); + + if (Number.isFinite(minsg)) { + setMinBuyAmount(minsg); + } else { + setMinBuyAmount(0); + } + + if (Number.isFinite(rate)) { + setFeeRate((prev) => { + const prevNum = parseNumberish(prev); + const shouldOverride = prev === '' || prev === '0' || prevNum === 0 || prevNum === null; + return shouldOverride ? rate.toFixed(2) : prev; + }); + } + }) + .catch((e) => { + if (cancelled) return; + setBuyMetaError(e?.message || '买入信息加载失败'); + setMinBuyAmount(0); + }) + .finally(() => { + if (cancelled) return; + setLoadingBuyMeta(false); + }); + + return () => { + cancelled = true; + }; + }, [isBuy, fund?.code]); + const currentPendingTrades = useMemo(() => { return pendingTrades.filter(t => t.fundCode === fund?.code); }, [pendingTrades, fund]); @@ -148,7 +202,7 @@ export default function TradeModal({ type, fund, holding, onClose, onConfirm, pe }; const isValid = isBuy - ? (!!amount && !!feeRate && !!date && calcShare !== null) + ? (!!amount && !!feeRate && !!date && calcShare !== null && !loadingBuyMeta && (parseFloat(amount) || 0) >= (Number(minBuyAmount) || 0)) : (!!share && !!date); const handleSetShareFraction = (fraction) => { @@ -372,72 +426,112 @@ export default function TradeModal({ type, fund, holding, onClose, onConfirm, pe
{isBuy ? ( <> -
- -
- -
-
+
+
+
+ +
0 && (parseFloat(amount) || 0) < Number(minBuyAmount))) + ? '1px solid var(--danger)' + : '1px solid var(--border)', + borderRadius: 12 + }} + > + 0 ? `最少 ¥${Number(minBuyAmount)},请输入加仓金额` : '请输入加仓金额'} + /> +
+ {(Number(minBuyAmount) || 0) > 0 && ( +
+ 最小加仓金额:¥{Number(minBuyAmount)} +
+ )} +
-
-
- -
- +
+
+ +
+ +
+
+
+ + +
+
+ +
+ +
+ + +
+
+ +
+ {buyMetaError ? ( + {buyMetaError} + ) : null} + {loadingPrice ? ( + 正在查询净值数据... + ) : price === 0 ? null : ( +
+ 参考净值: {Number(price).toFixed(4)} +
+ )}
-
- - -
-
-
- -
- - -
-
- -
- {loadingPrice ? ( - 正在查询净值数据... - ) : price === 0 ? null : ( -
- 参考净值: {Number(price).toFixed(4)} + + 正在加载买入费率/最小金额...
)}
@@ -564,8 +658,8 @@ export default function TradeModal({ type, fund, holding, onClose, onConfirm, pe diff --git a/components/ui/spinner.jsx b/components/ui/spinner.jsx new file mode 100644 index 0000000..3c7eafe --- /dev/null +++ b/components/ui/spinner.jsx @@ -0,0 +1,21 @@ +import { Loader2Icon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Spinner({ + className, + ...props +}) { + return ( + + ); +} + +export { Spinner } diff --git a/package-lock.json b/package-lock.json index 3920354..4947b0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5923,7 +5923,7 @@ }, "node_modules/class-variance-authority": { "version": "0.7.1", - "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "resolved": "https://registry.npmmirror.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz", "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", "license": "Apache-2.0", "dependencies": {