fix: 修复登录令牌和nginx中/test_api_connection接口
This commit is contained in:
@@ -72,6 +72,15 @@ server {
|
|||||||
proxy_read_timeout 300s;
|
proxy_read_timeout 300s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /test_api_connection {
|
||||||
|
proxy_pass http://backend:8888/test_api_connection;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
}
|
||||||
|
|
||||||
# 所有其他路由返回index.html(SPA应用需要)
|
# 所有其他路由返回index.html(SPA应用需要)
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
|
|||||||
59
frontend/src/assets/css/global.css
Normal file
59
frontend/src/assets/css/global.css
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/* 全局样式覆盖 - 专门针对Naive UI组件 */
|
||||||
|
|
||||||
|
/* 移除所有Naive UI按钮的焦点边框 */
|
||||||
|
.n-button:focus,
|
||||||
|
.n-button:focus-visible {
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 为主要按钮添加自定义焦点样式 */
|
||||||
|
.n-button--primary:focus,
|
||||||
|
.n-button--primary:focus-visible {
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: 0 0 0 2px rgba(32, 128, 240, 0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 输入框和下拉菜单的焦点样式 */
|
||||||
|
.n-input:focus-within,
|
||||||
|
.n-input-number:focus-within,
|
||||||
|
.n-select:focus-within {
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: 0 0 0 2px rgba(32, 128, 240, 0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标签的焦点样式 */
|
||||||
|
.n-tag:focus {
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移除所有元素的黑色边框 */
|
||||||
|
*:focus {
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移除移动设备上的点击高亮 */
|
||||||
|
* {
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 修复Firefox特定的焦点样式 */
|
||||||
|
button::-moz-focus-inner,
|
||||||
|
input::-moz-focus-inner {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确保所有按钮在点击后不显示边框 */
|
||||||
|
button:focus {
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确保所有输入框在点击后显示自定义边框 */
|
||||||
|
input:focus,
|
||||||
|
textarea:focus,
|
||||||
|
select:focus {
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: 0 0 0 2px rgba(32, 128, 240, 0.2) !important;
|
||||||
|
}
|
||||||
@@ -146,11 +146,17 @@
|
|||||||
|
|
||||||
<div class="api-actions">
|
<div class="api-actions">
|
||||||
<div class="api-save-option">
|
<div class="api-save-option">
|
||||||
<n-switch
|
<n-button
|
||||||
v-model:value="apiConfig.saveApiConfig"
|
tertiary
|
||||||
@update:value="handleConfigChange"
|
size="small"
|
||||||
/>
|
@click="saveConfig"
|
||||||
<span class="save-label">保存配置到本地</span>
|
round
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<n-icon :component="SaveIcon" />
|
||||||
|
</template>
|
||||||
|
保存配置到本地
|
||||||
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="api-buttons">
|
<div class="api-buttons">
|
||||||
@@ -188,7 +194,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue';
|
import { ref, computed, onMounted, markRaw } from 'vue';
|
||||||
import {
|
import {
|
||||||
NButton,
|
NButton,
|
||||||
NIcon,
|
NIcon,
|
||||||
@@ -219,12 +225,14 @@ import {
|
|||||||
RemoveOutline as RemoveIcon,
|
RemoveOutline as RemoveIcon,
|
||||||
CheckmarkCircle as SuccessIcon,
|
CheckmarkCircle as SuccessIcon,
|
||||||
CloseCircle as ErrorIcon,
|
CloseCircle as ErrorIcon,
|
||||||
CodeSlashOutline as CodeIcon
|
CodeSlashOutline as CodeIcon,
|
||||||
|
SaveOutline as SaveIcon
|
||||||
} from '@vicons/ionicons5';
|
} from '@vicons/ionicons5';
|
||||||
import { apiService } from '@/services/api';
|
import { apiService } from '@/services/api';
|
||||||
import { saveApiConfigToLocalStorage, loadApiConfig } from '@/utils';
|
import { saveApiConfigToLocalStorage, loadApiConfig } from '@/utils';
|
||||||
import type { ApiConfig } from '@/types';
|
import type { ApiConfig } from '@/types';
|
||||||
|
|
||||||
|
// 出于安全考虑,API KEY不会显示在前端
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
defaultApiUrl?: string;
|
defaultApiUrl?: string;
|
||||||
defaultApiModel?: string;
|
defaultApiModel?: string;
|
||||||
@@ -276,7 +284,7 @@ const apiConfig = ref<ApiConfig>({
|
|||||||
apiKey: '',
|
apiKey: '',
|
||||||
apiModel: props.defaultApiModel || '',
|
apiModel: props.defaultApiModel || '',
|
||||||
apiTimeout: props.defaultApiTimeout || '60',
|
apiTimeout: props.defaultApiTimeout || '60',
|
||||||
saveApiConfig: false
|
saveApiConfig: true // 默认启用保存配置
|
||||||
});
|
});
|
||||||
|
|
||||||
const apiTimeout = computed({
|
const apiTimeout = computed({
|
||||||
@@ -301,39 +309,35 @@ function toggleConfig() {
|
|||||||
function handleConfigChange() {
|
function handleConfigChange() {
|
||||||
console.log('API配置变更:', apiConfig.value);
|
console.log('API配置变更:', apiConfig.value);
|
||||||
|
|
||||||
// 如果选择了保存配置,则自动保存
|
// 不再在每次配置变更时自动保存
|
||||||
if (apiConfig.value.saveApiConfig) {
|
// 仅向父组件发送更新事件
|
||||||
saveApiConfigToLocalStorage({
|
|
||||||
apiUrl: apiConfig.value.apiUrl,
|
|
||||||
apiKey: apiConfig.value.apiKey,
|
|
||||||
apiModel: apiConfig.value.apiModel,
|
|
||||||
apiTimeout: apiConfig.value.apiTimeout,
|
|
||||||
saveApiConfig: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 向父组件发送更新事件
|
|
||||||
emit('update:apiConfig', { ...apiConfig.value });
|
emit('update:apiConfig', { ...apiConfig.value });
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTimeoutChange(value: number | null) {
|
function handleTimeoutChange(value: number | null) {
|
||||||
if (value !== null) {
|
if (value !== null) {
|
||||||
apiConfig.value.apiTimeout = value.toString();
|
apiConfig.value.apiTimeout = value.toString();
|
||||||
handleConfigChange();
|
|
||||||
|
// 仅通知父组件更新,不自动保存
|
||||||
|
emit('update:apiConfig', { ...apiConfig.value });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function increaseTimeout() {
|
function increaseTimeout() {
|
||||||
if (apiTimeout.value < 300) {
|
if (apiTimeout.value < 300) {
|
||||||
apiTimeout.value += 10;
|
apiTimeout.value += 10;
|
||||||
handleTimeoutChange(apiTimeout.value);
|
|
||||||
|
// 通知父组件
|
||||||
|
emit('update:apiConfig', { ...apiConfig.value });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function decreaseTimeout() {
|
function decreaseTimeout() {
|
||||||
if (apiTimeout.value > 10) {
|
if (apiTimeout.value > 10) {
|
||||||
apiTimeout.value -= 10;
|
apiTimeout.value -= 10;
|
||||||
handleTimeoutChange(apiTimeout.value);
|
|
||||||
|
// 通知父组件
|
||||||
|
emit('update:apiConfig', { ...apiConfig.value });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,25 +381,14 @@ async function testConnection() {
|
|||||||
connectionStatus.value = {
|
connectionStatus.value = {
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message: '连接成功!API配置有效。',
|
message: '连接成功!API配置有效。',
|
||||||
icon: SuccessIcon
|
icon: markRaw(SuccessIcon)
|
||||||
};
|
};
|
||||||
|
|
||||||
// 如果选择了保存配置,则保存
|
|
||||||
if (apiConfig.value.saveApiConfig) {
|
|
||||||
saveApiConfigToLocalStorage({
|
|
||||||
apiUrl: apiConfig.value.apiUrl,
|
|
||||||
apiKey: apiConfig.value.apiKey,
|
|
||||||
apiModel: apiConfig.value.apiModel,
|
|
||||||
apiTimeout: apiConfig.value.apiTimeout,
|
|
||||||
saveApiConfig: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
message.error(`API连接测试失败: ${response.message}`);
|
message.error(`API连接测试失败: ${response.message}`);
|
||||||
connectionStatus.value = {
|
connectionStatus.value = {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: `连接失败: ${response.message}`,
|
message: `连接失败: ${response.message}`,
|
||||||
icon: ErrorIcon
|
icon: markRaw(ErrorIcon)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -403,7 +396,7 @@ async function testConnection() {
|
|||||||
connectionStatus.value = {
|
connectionStatus.value = {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: `连接错误: ${error.message || '未知错误'}`,
|
message: `连接错误: ${error.message || '未知错误'}`,
|
||||||
icon: ErrorIcon
|
icon: markRaw(ErrorIcon)
|
||||||
};
|
};
|
||||||
} finally {
|
} finally {
|
||||||
testingConnection.value = false;
|
testingConnection.value = false;
|
||||||
@@ -416,16 +409,18 @@ function resetConfig() {
|
|||||||
apiKey: '',
|
apiKey: '',
|
||||||
apiModel: props.defaultApiModel || '',
|
apiModel: props.defaultApiModel || '',
|
||||||
apiTimeout: props.defaultApiTimeout || '60',
|
apiTimeout: props.defaultApiTimeout || '60',
|
||||||
saveApiConfig: false
|
saveApiConfig: true
|
||||||
};
|
};
|
||||||
|
|
||||||
// 清除本地存储
|
// 清除本地存储
|
||||||
if (window.localStorage) {
|
if (window.localStorage) {
|
||||||
localStorage.removeItem('apiConfig');
|
localStorage.removeItem('apiConfig');
|
||||||
|
message.success('已重置API配置并清除本地存储');
|
||||||
|
} else {
|
||||||
|
message.success('已重置API配置');
|
||||||
}
|
}
|
||||||
|
|
||||||
connectionStatus.value = null;
|
connectionStatus.value = null;
|
||||||
message.success('已重置API配置');
|
|
||||||
emit('update:apiConfig', { ...apiConfig.value });
|
emit('update:apiConfig', { ...apiConfig.value });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,7 +428,21 @@ function resetConfig() {
|
|||||||
function selectModel(key: string) {
|
function selectModel(key: string) {
|
||||||
console.log('选择模型:', key);
|
console.log('选择模型:', key);
|
||||||
apiConfig.value.apiModel = key;
|
apiConfig.value.apiModel = key;
|
||||||
handleConfigChange();
|
|
||||||
|
// 通知父组件
|
||||||
|
emit('update:apiConfig', { ...apiConfig.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显式保存配置的函数
|
||||||
|
function saveConfig() {
|
||||||
|
saveApiConfigToLocalStorage({
|
||||||
|
apiUrl: apiConfig.value.apiUrl,
|
||||||
|
apiKey: apiConfig.value.apiKey,
|
||||||
|
apiModel: apiConfig.value.apiModel,
|
||||||
|
apiTimeout: apiConfig.value.apiTimeout,
|
||||||
|
saveApiConfig: true
|
||||||
|
});
|
||||||
|
message.success('已将配置保存到本地');
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -528,15 +537,27 @@ onMounted(() => {
|
|||||||
.api-save-option {
|
.api-save-option {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: rgba(0, 0, 0, 0.02);
|
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.save-label {
|
.api-save-option button {
|
||||||
margin-left: 0.5rem;
|
display: flex;
|
||||||
font-size: 0.875rem;
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px 16px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
background-color: rgba(0, 0, 0, 0.02);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-save-option button:hover {
|
||||||
|
background-color: rgba(32, 128, 240, 0.08);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-save-option button .n-icon {
|
||||||
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.api-buttons {
|
.api-buttons {
|
||||||
|
|||||||
@@ -603,16 +603,32 @@ async function analyzeStocks() {
|
|||||||
requestData.api_timeout = apiConfig.value.apiTimeout;
|
requestData.api_timeout = apiConfig.value.apiTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取身份验证令牌
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
|
||||||
|
// 构建请求头
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果有令牌,添加到请求头
|
||||||
|
if (token) {
|
||||||
|
headers['Authorization'] = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
|
||||||
// 发送分析请求
|
// 发送分析请求
|
||||||
const response = await fetch('/analyze', {
|
const response = await fetch('/analyze', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers,
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(requestData)
|
body: JSON.stringify(requestData)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
if (response.status === 401) {
|
||||||
|
message.error('未授权访问,请登录后再试');
|
||||||
|
// 可以在这里触发登录流程
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (response.status === 404) {
|
if (response.status === 404) {
|
||||||
throw new Error('服务器接口未找到,请检查服务是否正常运行');
|
throw new Error('服务器接口未找到,请检查服务是否正常运行');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import './style.css'
|
import './style.css'
|
||||||
|
import './assets/css/global.css'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ button:hover {
|
|||||||
}
|
}
|
||||||
button:focus,
|
button:focus,
|
||||||
button:focus-visible {
|
button:focus-visible {
|
||||||
outline: 4px auto -webkit-focus-ring-color;
|
outline: none !important;
|
||||||
|
box-shadow: 0 0 0 2px rgba(32, 128, 240, 0.2) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
@@ -77,3 +78,38 @@ button:focus-visible {
|
|||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 全局移除黑色边框 */
|
||||||
|
*:focus {
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 输入框和表单元素的焦点样式 */
|
||||||
|
input:focus,
|
||||||
|
textarea:focus,
|
||||||
|
select:focus {
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: 0 0 0 2px rgba(32, 128, 240, 0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移除移动设备上的点击高亮 */
|
||||||
|
* {
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 修复Firefox特定的焦点样式 */
|
||||||
|
button::-moz-focus-inner,
|
||||||
|
input::-moz-focus-inner {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Naive UI组件的焦点样式 */
|
||||||
|
.n-button:focus,
|
||||||
|
.n-button:focus-visible,
|
||||||
|
.n-input:focus-within,
|
||||||
|
.n-input-number:focus-within,
|
||||||
|
.n-select:focus-within,
|
||||||
|
.n-tag:focus {
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: 0 0 0 2px rgba(32, 128, 240, 0.2) !important;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user