ADD: 支持ETF、 LOF基金!

This commit is contained in:
兰志宏
2025-03-05 16:32:30 +08:00
parent f5ebe29782
commit 25601b9bd6
4 changed files with 273 additions and 93 deletions

View File

@@ -301,29 +301,28 @@
<option value="A">A股</option>
<option value="HK">港股</option>
<option value="US">美股</option>
<option value="ETF">ETF基金</option>
<option value="LOF">LOF基金</option>
</select>
</div>
<!-- 美股搜索框 -->
<div id="usStockSearch" class="mb-4 hidden">
<!-- 搜索框 -->
<div id="searchContainer" class="mb-4 hidden">
<div class="relative">
<input type="text"
id="searchInput"
class="w-full p-2 border rounded bg-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="输入美股名称搜索(中文和英文都试下)"
oninput="handleSearchInput(event)">
<!-- 添加搜索 loading 图标 -->
placeholder="输入名称回车搜索"
onkeydown="handleKeyDown(event)">
<div id="searchLoading" class="absolute right-3 top-2.5 hidden">
<svg class="animate-spin h-5 w-5 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
<!-- 搜索结果下拉框 -->
<div id="searchResults"
class="absolute z-10 w-full mt-1 bg-white border rounded-md shadow-lg hidden">
<div id="searchResults" class="absolute z-10 w-full mt-1 bg-white border rounded-md shadow-lg hidden max-h-80 overflow-y-auto">
</div>
</div>
<!-- 添加错误提示 -->
<div id="searchError" class="hidden absolute z-10 w-full mt-1 p-3 bg-red-50 text-red-600 rounded-md border border-red-200">
</div>
</div>
@@ -342,6 +341,17 @@
};
}
function handleKeyDown(event) {
// 检查是否按下回车键且不在输入法编辑状态
if (event.key === 'Enter' && !event.isComposing) {
event.preventDefault(); // 阻止默认行为
const keyword = event.target.value.trim();
if (keyword) {
debouncedSearch(keyword);
}
}
}
// 使用防抖包装搜索函数
const debouncedSearch = debounce(async (keyword) => {
if (!keyword) {
@@ -350,20 +360,26 @@
return;
}
// 显示 loading
const marketType = document.getElementById('marketType').value;
document.getElementById('searchLoading').classList.remove('hidden');
// 隐藏之前的错误信息
document.getElementById('searchError').classList.add('hidden');
try {
const response = await fetch(`/search_us_stocks?keyword=${encodeURIComponent(keyword)}`);
let endpoint = '';
if (marketType === 'US') {
endpoint = '/search_us_stocks';
} else if (['ETF', 'LOF'].includes(marketType)) {
endpoint = '/search_funds';
}
const response = await fetch(`${endpoint}?market_type=${marketType}&keyword=${encodeURIComponent(keyword)}`);
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || '搜索失败');
}
displaySearchResults(data.results);
displaySearchResults(data.results, marketType);
} catch (error) {
console.error('搜索出错:', error);
const errorDiv = document.getElementById('searchError');
@@ -373,12 +389,8 @@
} finally {
document.getElementById('searchLoading').classList.add('hidden');
}
}, 500); // 设置500ms的防抖延迟
}, 500);
// 修改输入事件处理函数
function handleSearchInput(event) {
debouncedSearch(event.target.value);
}
</script>
<div class="mb-4">
<label for="batchStocks" class="block text-sm font-medium text-gray-700 mb-2">
@@ -395,12 +407,28 @@
function handleMarketTypeChange() {
const marketType = document.getElementById('marketType').value;
const searchDiv = document.getElementById('usStockSearch');
searchDiv.classList.toggle('hidden', marketType !== 'US');
const searchContainer = document.getElementById('searchContainer');
const searchInput = document.getElementById('searchInput');
const batchStocks = document.getElementById('batchStocks');
// 清空搜索结果
// 显示/隐藏搜索框
searchContainer.classList.toggle('hidden', !['US', 'ETF', 'LOF'].includes(marketType));
// 更新搜索框提示文本
if (marketType === 'US') {
searchInput.placeholder = '输入美股名称回车搜索(中文和英文都试下)';
} else if (marketType === 'ETF') {
searchInput.placeholder = '输入ETF基金名称回车搜索';
} else if (marketType === 'LOF') {
searchInput.placeholder = '输入LOF基金名称回车搜索';
}
// 清空搜索结果和输入
document.getElementById('searchResults').innerHTML = '';
document.getElementById('searchInput').value = '';
searchInput.value = '';
// 清空代码输入框
batchStocks.value = '';
}
function debounceSearch(event) {
@@ -410,7 +438,7 @@
}, 300);
}
function displaySearchResults(results) {
function displaySearchResults(results, marketType) {
const resultsDiv = document.getElementById('searchResults');
if (!results || results.length === 0) {
@@ -418,18 +446,32 @@
return;
}
let html = '<div class="py-1">';
results.forEach(stock => {
let html = '<div class="divide-y divide-gray-100">'; // 添加分割线
results.forEach(item => {
let rightContent = '';
if (marketType === 'US') {
rightContent = `
<div class="font-medium">$${item.price}</div>
<div class="text-sm text-gray-500">市值: ${formatMarketValue(item.market_value)}</div>
`;
} else {
rightContent = `
<div class="font-medium">¥${item.price}</div>
<div class="text-sm text-gray-500">
${item.discount_rate ? `折价率: ${item.discount_rate}%` : ''}
</div>
`;
}
html += `
<div class="px-4 py-2 hover:bg-gray-100 cursor-pointer flex justify-between items-center"
onclick="selectStock('${stock.symbol}')">
onclick="selectStock('${item.symbol}')">
<div>
<div class="font-medium">${stock.name}</div>
<div class="text-sm text-gray-500">${stock.symbol}</div>
<div class="font-medium">${item.name}</div>
<div class="text-sm text-gray-500">${item.symbol}</div>
</div>
<div class="text-right">
<div class="font-medium">$${stock.price}</div>
<div class="text-sm text-gray-500">市值: ${formatMarketValue(stock.market_value)}</div>
${rightContent}
</div>
</div>
`;
@@ -686,10 +728,10 @@
errorCard = document.createElement('div');
errorCard.id = `error-${stockCode}`;
errorCard.className = 'bg-red-50 p-4 rounded-lg text-red-600';
errorCard.innerHTML = `分析股票 ${stockCode} 出错: ${chunk.error}`;
errorCard.innerHTML = `分析 ${stockCode} 出错: ${chunk.error}`;
container.appendChild(errorCard);
} else {
errorCard.innerHTML = `分析股票 ${stockCode} 出错: ${chunk.error}`;
errorCard.innerHTML = `分析 ${stockCode} 出错: ${chunk.error}`;
}
return;
}