feat(ui): 增加历史价格批量导入解析功能与底层 Upsert 接口

This commit is contained in:
2026-04-30 10:25:09 +08:00
parent 108023ae67
commit 209cdd3625
3 changed files with 181 additions and 15 deletions
+57 -1
View File
@@ -2,9 +2,10 @@
import { ProxyAgent, setGlobalDispatcher } from 'undici';
import { db } from '@/db';
import { assets } from '@/db/schema';
import { assets, assetPricesHistory } from '@/db/schema';
import { eq } from 'drizzle-orm';
import { revalidatePath } from 'next/cache';
import { onConflictDoUpdate } from 'drizzle-orm/pg-core';
function getTencentSymbol(asset: { symbol: string; exchange: string | null }): string {
const cleanSymbol = asset.symbol.trim().toUpperCase().replace(/[^0-9A-Z]/g, '');
@@ -80,3 +81,58 @@ export async function syncAllMarketPrices() {
return { success: true, count: successCount };
}
export async function importHistoricalPrices(
assetId: string,
data: Array<{ date: string; price: string }>
) {
if (!assetId || !data || data.length === 0) {
return { success: false, error: '參數不完整' };
}
let successCount = 0;
let errorCount = 0;
for (const row of data) {
try {
const parsedDate = row.date.trim();
const parsedPrice = row.price.trim();
if (!parsedDate || !parsedPrice) continue;
const priceNum = Number(parsedPrice);
if (isNaN(priceNum) || priceNum < 0) continue;
const dateMatch = parsedDate.match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!dateMatch) continue;
const normalizedDate = `${dateMatch[1]}-${dateMatch[2]}-${dateMatch[3]}`;
await db
.insert(assetPricesHistory)
.values({
assetId,
price: parsedPrice,
date: normalizedDate,
})
.onConflictDoUpdate({
target: [assetPricesHistory.assetId, assetPricesHistory.date],
set: { price: parsedPrice },
});
successCount++;
} catch (error) {
errorCount++;
console.warn(`[批量導入] 導入 ${row.date} 失敗:`, error);
}
}
revalidatePath('/dashboard');
revalidatePath('/dashboard/assets');
return {
success: true,
imported: successCount,
errors: errorCount,
};
}