feat(api): 接入币安公共接口,升级为股票+加密货币的双轨制全市场行情引擎
This commit is contained in:
parent
80029817a9
commit
48e834e338
@ -17,16 +17,16 @@ function getTencentSymbol(asset: { symbol: string; exchange: string | null }): s
|
||||
}
|
||||
}
|
||||
|
||||
export async function syncAllStockPrices() {
|
||||
const stockAssets = await db
|
||||
export async function syncAllMarketPrices() {
|
||||
const allAssets = await db
|
||||
.select()
|
||||
.from(assets)
|
||||
.where(eq(assets.type, 'STOCK'));
|
||||
.from(assets);
|
||||
|
||||
let successCount = 0;
|
||||
|
||||
for (const asset of stockAssets) {
|
||||
for (const asset of allAssets) {
|
||||
try {
|
||||
if (asset.type === 'STOCK') {
|
||||
const tCode = getTencentSymbol(asset);
|
||||
const response = await fetch(`https://qt.gtimg.cn/q=${tCode}`, { cache: 'no-store' });
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
@ -46,6 +46,23 @@ export async function syncAllStockPrices() {
|
||||
successCount++;
|
||||
}
|
||||
}
|
||||
} else if (asset.type === 'CRYPTO') {
|
||||
const cryptoSymbol = asset.symbol.trim().toUpperCase() + 'USDT';
|
||||
const response = await fetch(`https://api.binance.com/api/v3/ticker/price?symbol=${cryptoSymbol}`, { cache: 'no-store' });
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.price) {
|
||||
await db.update(assets)
|
||||
.set({
|
||||
latestPrice: data.price,
|
||||
name: asset.symbol.toUpperCase(),
|
||||
})
|
||||
.where(eq(assets.id, asset.id));
|
||||
successCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`[行情引擎] 同步 ${asset.symbol} 失败,保持原价:`, error);
|
||||
}
|
||||
|
||||
@ -67,6 +67,7 @@ export function AddAssetDialog() {
|
||||
});
|
||||
|
||||
const exchangeValue = useWatch({ control: form.control, name: 'exchange' });
|
||||
const typeValue = useWatch({ control: form.control, name: 'type' });
|
||||
|
||||
useEffect(() => {
|
||||
if (!exchangeValue) return;
|
||||
@ -82,6 +83,13 @@ export function AddAssetDialog() {
|
||||
}
|
||||
}, [exchangeValue, form]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeValue === 'CRYPTO') {
|
||||
form.setValue('exchange', 'CRYPTO', { shouldValidate: true });
|
||||
form.setValue('baseCurrency', 'USD', { shouldValidate: true });
|
||||
}
|
||||
}, [typeValue, form]);
|
||||
|
||||
function onSubmit(values: AddAssetForm) {
|
||||
startTransition(async () => {
|
||||
const result = await createAsset(values);
|
||||
@ -149,7 +157,7 @@ export function AddAssetDialog() {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>交易所 / 市场 (Exchange)</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value} disabled={typeValue === 'CRYPTO'}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择交易所" />
|
||||
|
||||
@ -3,21 +3,21 @@
|
||||
import { useState, useTransition } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
import { syncAllStockPrices } from '@/actions/market';
|
||||
import { syncAllMarketPrices } from '@/actions/market';
|
||||
|
||||
export function SyncButton() {
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
function handleClick() {
|
||||
startTransition(async () => {
|
||||
await syncAllStockPrices();
|
||||
await syncAllMarketPrices();
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Button onClick={handleClick} disabled={isPending}>
|
||||
<RefreshCw className={`h-4 w-4 mr-2 ${isPending ? 'animate-spin' : ''}`} />
|
||||
{isPending ? '同步中...' : '同步股票行情'}
|
||||
{isPending ? '同步中...' : '同步全市场行情'}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user