fix: 统一前后端API_URL处理规则: /结尾忽略v1版本,#结尾强制使用输入地址。 优化公告区域位置

This commit is contained in:
兰志宏
2025-03-04 18:24:37 +08:00
parent 661741e25d
commit 0847e557fb
6 changed files with 282 additions and 42 deletions

View File

@@ -7,28 +7,202 @@
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
</head>
<body class="bg-gray-100">
<!-- 添加公告栏 -->
{% if announcement %}
<div id="announcement-container" class="fixed top-4 right-4 max-w-md z-50 animate-fade-in-down">
<div class="bg-blue-50 border-l-4 border-blue-500 p-4 rounded-lg shadow-lg">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-blue-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3 pr-8">
<p class="text-sm text-blue-700 whitespace-pre-line" id="announcement-text"></p>
<p class="text-xs text-gray-500 mt-1" id="announcement-timer"></p>
</div>
<button onclick="closeAnnouncement()" class="absolute top-2 right-2 text-gray-400 hover:text-gray-600">
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
<script>
// 处理公告文本中的URL
const announcementText = `{{ announcement }}`;
const urlRegex = /(https?:\/\/[^\s]+)/g;
document.getElementById('announcement-text').innerHTML = announcementText.replace(
urlRegex,
'<a href="$1" target="_blank" class="text-blue-600 hover:text-blue-800 underline">$1</a>'
);
// 设置自动关闭时间5秒
const autoCloseTime = 5;
let remainingTime = autoCloseTime;
const timerElement = document.getElementById('announcement-timer');
// 更新倒计时显示
function updateTimer() {
timerElement.textContent = `${remainingTime}秒后自动关闭`;
if (remainingTime <= 0) {
closeAnnouncement();
} else {
remainingTime--;
setTimeout(updateTimer, 1000);
}
}
// 启动倒计时
updateTimer();
// 关闭公告的函数
function closeAnnouncement() {
const container = document.getElementById('announcement-container');
container.style.opacity = '0';
container.style.transform = 'translateY(-10px)';
container.style.transition = 'all 0.3s ease-out';
setTimeout(() => {
container.remove();
}, 300);
}
</script>
{% endif %}
<div class="container mx-auto px-4 py-8">
<h1 class="text-3xl font-bold mb-8 text-center">股票分析系统</h1>
<!-- 添加公告栏 -->
{% if announcement %}
<!-- 添加市场时间显示 -->
<div class="max-w-4xl mx-auto mb-8">
<div class="bg-blue-50 border-l-4 border-blue-500 p-4 rounded">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-blue-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
</svg>
<div class="bg-white rounded-lg shadow-md p-6">
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
<!-- 当前时间 -->
<div class="text-center">
<p class="text-gray-600 mb-2">当前时间</p>
<p id="currentTime" class="text-2xl font-bold text-gray-800"></p>
</div>
<div class="ml-3">
<p class="text-sm text-blue-700">
{{ announcement }}
</p>
<!-- A股状态 -->
<div class="text-center">
<p class="text-gray-600 mb-2">A股市场</p>
<p id="cnMarketStatus" class="text-lg font-medium"></p>
<p id="cnMarketTimer" class="text-sm text-gray-500 mt-1"></p>
</div>
<!-- 港股状态 -->
<div class="text-center">
<p class="text-gray-600 mb-2">港股市场</p>
<p id="hkMarketStatus" class="text-lg font-medium"></p>
<p id="hkMarketTimer" class="text-sm text-gray-500 mt-1"></p>
</div>
<!-- 美股状态 -->
<div class="text-center">
<p class="text-gray-600 mb-2">美股市场</p>
<p id="usMarketStatus" class="text-lg font-medium"></p>
<p id="usMarketTimer" class="text-sm text-gray-500 mt-1"></p>
</div>
</div>
</div>
</div>
{% endif %}
<script>
// 更新时间显示
function updateTime() {
const now = new Date();
// 更新当前时间
document.getElementById('currentTime').textContent =
now.toLocaleTimeString('zh-CN', { hour12: false });
// 获取中国时间
const cnOptions = { timeZone: 'Asia/Shanghai', hour12: false };
const cnTime = now.toLocaleString('zh-CN', cnOptions);
const cnHour = new Date(cnTime).getHours();
const cnMinute = new Date(cnTime).getMinutes();
// A股市场状态
const cnMarketOpen = (cnHour === 9 && cnMinute >= 30) || (cnHour === 10) ||
(cnHour === 11 && cnMinute <= 30) ||
(cnHour >= 13 && cnHour < 15);
updateMarketStatus('cn', cnMarketOpen, cnHour, cnMinute, 9, 30, 15, 0);
// 港股市场状态与A股相同时区
const hkMarketOpen = (cnHour === 9 && cnMinute >= 30) ||
(cnHour === 10) || (cnHour === 11) ||
(cnHour >= 13 && cnHour < 16);
updateMarketStatus('hk', hkMarketOpen, cnHour, cnMinute, 9, 30, 16, 0);
// 获取美国东部时间
const usOptions = { timeZone: 'America/New_York', hour12: false };
const usTime = now.toLocaleString('zh-CN', usOptions);
const usHour = new Date(usTime).getHours();
const usMinute = new Date(usTime).getMinutes();
// 美股市场状态
const usMarketOpen = (usHour >= 9 && usHour < 16) ||
(usHour === 16 && usMinute === 0);
updateMarketStatus('us', usMarketOpen, usHour, usMinute, 9, 30, 16, 0);
}
// 更新市场状态的通用函数
function updateMarketStatus(market, isOpen, currentHour, currentMinute, openHour, openMinute, closeHour, closeMinute) {
const element = document.getElementById(`${market}MarketStatus`);
const timer = document.getElementById(`${market}MarketTimer`);
if (isOpen) {
// ... 现有的开市逻辑保持不变 ...
} else {
element.textContent = '已休市';
element.className = 'text-lg font-medium text-gray-600';
// 计算距离下一个开盘时间
const now = new Date();
const nextOpen = new Date(now);
if (market === 'us') {
// 获取美东时间的下一个开盘时间
const usTime = new Date(now.toLocaleString('en-US', { timeZone: 'America/New_York' }));
const usHour = usTime.getHours();
// 如果当前美东时间已过收盘时间,设置为下一个交易日
if (usHour >= closeHour) {
nextOpen.setDate(nextOpen.getDate() + 1);
}
// 设置为美东时间的开盘时间
nextOpen.setHours(openHour + 12, openMinute, 0); // 加12小时转换为北京时间
} else {
// 其他市场的逻辑保持不变
if (currentHour >= closeHour) {
nextOpen.setDate(nextOpen.getDate() + 1);
}
nextOpen.setHours(openHour, openMinute, 0);
}
const timeToOpen = nextOpen - now;
const hours = Math.floor(timeToOpen / 3600000);
const minutes = Math.floor((timeToOpen % 3600000) / 60000);
// 确保显示的时间始终为正数
if (hours >= 0 && minutes >= 0) {
timer.textContent = `距离开盘还有 ${hours}小时${minutes}分钟`;
} else {
timer.textContent = '计算开盘时间中...';
}
}
}
// 每秒更新一次时间
setInterval(updateTime, 1000);
updateTime(); // 立即执行一次
</script>
<div class="max-w-4xl mx-auto">
<!-- 批量分析 -->
@@ -54,7 +228,9 @@
<input type="text" id="apiUrl"
class="w-full p-2 border rounded bg-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="例如: https://api.openai.com"
value="{{ default_api_url }}">
value="{{ default_api_url }}"
oninput="updateFormattedUrl(this.value)">
<p id="formattedUrl" class="mt-1 text-sm text-gray-500 break-all"></p>
</div>
<div>
<label for="apiModel" class="block text-sm font-medium text-gray-700 mb-1">API 模型</label>
@@ -115,10 +291,10 @@
<div id="usStockSearch" 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="debounceSearch(event)">
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 图标 -->
<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">
@@ -138,7 +314,19 @@
<!-- 修改搜索相关函数 -->
<script>
async function searchUsStocks(keyword) {
// 创建防抖函数
function debounce(func, wait) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(this, args);
}, wait);
};
}
// 使用防抖包装搜索函数
const debouncedSearch = debounce(async (keyword) => {
if (!keyword) {
document.getElementById('searchResults').classList.add('hidden');
document.getElementById('searchError').classList.add('hidden');
@@ -161,16 +349,18 @@
displaySearchResults(data.results);
} catch (error) {
console.error('搜索出错:', error);
// 显示错误信息
const errorDiv = document.getElementById('searchError');
errorDiv.textContent = `搜索出错: ${error.message}`;
errorDiv.classList.remove('hidden');
// 隐藏搜索结果
document.getElementById('searchResults').classList.add('hidden');
} finally {
// 隐藏 loading
document.getElementById('searchLoading').classList.add('hidden');
}
}, 500); // 设置500ms的防抖延迟
// 修改输入事件处理函数
function handleSearchInput(event) {
debouncedSearch(event.target.value);
}
</script>
<div class="mb-4">
@@ -961,4 +1151,36 @@
});
</script>
</body>
</html>
</html>
<script>
function updateFormattedUrl(url) {
const formattedUrlElement = document.getElementById('formattedUrl');
if (!url) {
formattedUrlElement.textContent = '';
return;
}
let formattedUrl;
if (url.endsWith('/')) {
formattedUrl = `${url}chat/completions`;
} else if (url.endsWith('#')) {
formattedUrl = url.replace("#", "");
} else {
formattedUrl = `${url}/v1/chat/completions`;
}
formattedUrlElement.innerHTML = `
<span class="font-medium">/结尾忽略v1版本#结尾强制使用输入地址:</span><br>
<span class="text-blue-600">${formattedUrl}</span>
`;
}
// 页面加载时初始化显示
document.addEventListener('DOMContentLoaded', function() {
const apiUrl = document.getElementById('apiUrl');
if (apiUrl.value) {
updateFormattedUrl(apiUrl.value);
}
});
</script>