import { useState, useMemo, useEffect, useRef } from 'react'; import { Modal, Form, Input, InputNumber, Select, Switch, AutoComplete, Button, Alert, Tag, App as AntdApp, } from 'antd'; import { ReloadOutlined } from '@ant-design/icons'; import { positionService } from '@/services/position'; import { stockDataService, type AssetSearchResult } from '@/services/stock-data'; import { useBrokerStore } from '@/stores/broker'; import type { CreatePositionRequest } from '@/types/position'; // 简单的防抖函数 function debounce void>( func: T, wait: number ): (...args: Parameters) => void { let timeout: ReturnType | null = null; return function executedFunction(...args: Parameters) { const later = () => { timeout = null; func(...args); }; if (timeout) { clearTimeout(timeout); } timeout = setTimeout(later, wait); }; } interface CreatePositionModalProps { open: boolean; onCancel: () => void; onSuccess: () => void; } const CreatePositionModal = ({ open, onCancel, onSuccess }: CreatePositionModalProps) => { const [form] = Form.useForm(); const { message: messageApi } = AntdApp.useApp(); const [loading, setLoading] = useState(false); const [searchKeyword, setSearchKeyword] = useState(''); const [searchResults, setSearchResults] = useState([]); const [isSearching, setIsSearching] = useState(false); const [selectedAsset, setSelectedAsset] = useState(null); const [assetType, setAssetType] = useState(''); const [showManualInput, setShowManualInput] = useState(false); const nameInputRef = useRef(null); const brokers = useBrokerStore((state) => state.brokers); // 初始化股票数据(弹窗打开时) useEffect(() => { if (open) { stockDataService.init().catch((error) => { console.error('初始化股票数据失败', error); messageApi.warning('股票数据加载失败,搜索功能可能不可用'); }); } }, [open, messageApi]); // 防抖搜索(前端字符串匹配) const debouncedSearch = useMemo( () => debounce((keyword: string) => { if (!keyword || keyword.trim().length < 1) { setSearchResults([]); setIsSearching(false); return; } setIsSearching(true); try { // 在前端进行字符串匹配 const results = stockDataService.searchAssets(keyword, 10); setSearchResults(results); } catch (error: any) { console.error('搜索失败', error); setSearchResults([]); messageApi.error(error.message || '搜索失败'); } finally { setIsSearching(false); } }, 300), [messageApi] ); // 搜索资产 const handleSearch = (keyword: string) => { setSearchKeyword(keyword); debouncedSearch(keyword); }; // 选择资产 const handleSelectAsset = (asset: AssetSearchResult) => { setSelectedAsset(asset); setSearchKeyword(asset.name); setSearchResults([]); setShowManualInput(false); // 统一市场代码:sh/sz/bj -> 'a' (A股) // 港股和美股直接使用 const formMarket = asset.market === 'a' ? 'a' : asset.market; // 自动填充表单 form.setFieldsValue({ assetType: 'stock', // 能匹配上的一定是股票 market: formMarket, symbol: asset.symbol, name: asset.name, currency: asset.market === 'hk' ? 'HKD' : asset.market === 'us' ? 'USD' : 'CNY', }); setAssetType('stock'); }; // 重新选择 const handleResetSearch = () => { setSelectedAsset(null); setSearchKeyword(''); setSearchResults([]); form.setFieldsValue({ assetType: assetType || undefined, market: undefined, symbol: undefined, name: undefined, }); }; // 资产类型变化 const handleAssetTypeChange = (value: string) => { setAssetType(value); form.setFieldsValue({ assetType: value }); // 如果手动修改了资产类型,清空搜索选择 if (selectedAsset && value !== 'stock') { handleResetSearch(); } // 如果选择了现金或其他,隐藏搜索框 if (value === 'cash' || value === 'other') { setShowManualInput(true); } else { setShowManualInput(false); } // 如果搜索框有内容,重新搜索 if (searchKeyword && value === 'stock') { handleSearch(searchKeyword); } }; // 获取搜索框占位符 const getSearchPlaceholder = () => { return '输入股票代码或名称搜索(如:600519 或 贵州茅台)'; }; // 提交表单 const handleSubmit = async (values: any) => { setLoading(true); try { // 如果市场是 'a'(A股),转换为 'sh'(默认使用上海市场) const marketValue = values.market === 'a' ? 'sh' : values.market; // 现金类型:symbol 为空字符串,name 固定为"现金" // 其他类型:symbol 为空字符串 const symbolValue = values.assetType === 'cash' || values.assetType === 'other' ? '' : values.symbol || ''; const nameValue = values.assetType === 'cash' ? '现金' : values.name || ''; const requestData: CreatePositionRequest = { // 其他类型不需要 brokerId 和 market brokerId: values.assetType === 'other' ? undefined : values.brokerId, assetType: values.assetType, symbol: symbolValue, name: nameValue, market: values.assetType === 'other' || values.assetType === 'cash' ? undefined : marketValue, // 现金类型不需要 shares 和 costPrice,使用默认值 shares: values.assetType === 'cash' ? 1 : values.shares, costPrice: values.assetType === 'cash' ? values.currentPrice || 0 : values.costPrice, currentPrice: values.currentPrice, currency: values.currency || 'CNY', autoPriceUpdate: values.autoPriceUpdate || false, status: 'active', }; const response = await positionService.createPosition(requestData); if (response.code === 0) { messageApi.success('创建持仓成功'); form.resetFields(); setSelectedAsset(null); setSearchKeyword(''); setAssetType(''); onSuccess(); onCancel(); } else { messageApi.error(response.message || '创建持仓失败'); } } catch (error: any) { console.error('创建持仓失败:', error); messageApi.error('创建持仓失败,请重试!'); } finally { setLoading(false); } }; // 重置表单 const handleCancel = () => { form.resetFields(); setSelectedAsset(null); setSearchKeyword(''); setSearchResults([]); setAssetType(''); setShowManualInput(false); onCancel(); }; // 市场选项 const marketOptions = [ { value: 'a', label: 'A股' }, { value: 'hk', label: '港股' }, { value: 'us', label: '美股' }, { value: 'jp', label: '日股' }, { value: 'kr', label: '韩国股市' }, { value: 'eu', label: '欧洲市场' }, { value: 'sea', label: '东南亚市场' }, { value: 'other', label: '其他' }, ]; // 获取市场显示名称 const getMarketDisplayName = (market: string) => { // 统一市场代码映射 if (market === 'a' || market === 'sh' || market === 'sz' || market === 'bj') { return 'A股'; } const option = marketOptions.find((opt) => opt.value === market); return option ? option.label : market; }; // 根据资产类型获取代码标签 const getCodeLabel = (type: string) => { switch (type) { case 'stock': return '股票代码'; case 'fund': return '基金代码'; case 'bond': return '债券代码'; default: return '资产代码'; } }; // 根据资产类型获取名称标签 const getNameLabel = (type: string) => { switch (type) { case 'stock': return '股票名称'; case 'fund': return '基金名称'; case 'bond': return '债券名称'; default: return '资产名称'; } }; return (
{/* 搜索框(现金和其他类型时隐藏) */} {!showManualInput && ( ({ value: `${asset.symbol} - ${asset.name}`, label: (
{asset.symbol} - {asset.name}
{getMarketDisplayName(asset.market)} - 股票
), asset: asset, }))} onChange={handleSearch} onSelect={(_: any, option: any) => handleSelectAsset(option.asset)} placeholder={getSearchPlaceholder()} allowClear disabled={!!selectedAsset} notFoundContent={ searchKeyword && !isSearching ? (
未找到匹配的资产
) : null } showSearch={true} /> {selectedAsset && (
{selectedAsset.name}
)}
)} {/* 资产类型 */} {/* 其他字段(有动画效果,只有在选择资产类型或选中资产后才显示) */}
{/* 市场和券商(股票/基金/债券显示) */} {(assetType === 'stock' || assetType === 'fund' || assetType === 'bond') && ( <> )} {/* 券商(现金显示) */} {assetType === 'cash' && ( )} {/* 其他类型:只显示名称、成本价、数量、最新价 */} {assetType === 'other' && ( <> )} {/* 价格和数量(现金和其他类型不显示成本价和持股数量) */} {assetType && assetType !== 'cash' && assetType !== 'other' && ( <> )} {/* 最新价/现金余额(其他类型已在上面单独处理) */} {assetType && assetType !== 'other' && ( )} {/* 其他选项 */} {assetType && ( <>

• 可以通过搜索框快速选择资产,系统会自动填充相关信息

• 成本价和最新价请使用人民币计价

• 如果持有港股/美股,请将港币/美元价格转换为人民币后输入

• 系统会自动更新价格(如果开启自动更新)

} type="info" showIcon style={{ marginBottom: 16 }} />
)}
); }; export default CreatePositionModal;