## 修复内容 ### 前端 (Frontend) - 修复 ESLint 错误:未使用变量重命名为下划线前缀 - 修复 TypeScript 类型错误:完善 BackendPhoto 接口定义 - 修复引号转义问题:搜索结果显示优化 - 优化 useEffect 依赖:添加 useCallback 避免无限循环 - 移除未使用的导入和变量 ### 后端 (Backend) - 修复 go vet 错误:测试文件中的字段名称不匹配 - 修复数组访问错误:使用正确的结构体字段路径 - 统一代码格式:go fmt 自动格式化 ### 管理后台 (Admin) - 创建缺失的 ESLint 配置文件 - 修复 React 导入缺失问题 - 确保 TypeScript 编译通过 ## CI/CD 改进 - 验证了前端、后端、管理后台的完整构建流程 - 所有 lint 检查、类型检查、测试均通过 - 为自动化部署做好准备 ## 技术细节 - 前端:修复 5+ ESLint 错误,完善类型定义 - 后端:修复 3+ go vet 错误,通过所有测试 - 管理后台:创建 ESLint 配置,修复导入问题 - 所有模块均可正常构建和运行
97 lines
2.9 KiB
TypeScript
97 lines
2.9 KiB
TypeScript
"use client"
|
|
|
|
import { useState, useEffect, useCallback } from 'react'
|
|
import { Badge } from './ui/badge'
|
|
import { Button } from './ui/button'
|
|
import { Alert, AlertDescription } from './ui/alert'
|
|
import { Wifi, WifiOff, RefreshCw, Settings } from 'lucide-react'
|
|
import api from '@/lib/api'
|
|
|
|
export function ApiStatus() {
|
|
const [isOnline, setIsOnline] = useState(false)
|
|
const [isLoading, setIsLoading] = useState(true)
|
|
const [useRealApi, setUseRealApi] = useState(process.env.NEXT_PUBLIC_USE_REAL_API === 'true')
|
|
const [apiUrl, setApiUrl] = useState('')
|
|
|
|
useEffect(() => {
|
|
if (useRealApi) {
|
|
setApiUrl(process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080/api/v1')
|
|
} else {
|
|
setApiUrl(process.env.NEXT_PUBLIC_MOCK_API_URL || 'http://localhost:3001/api')
|
|
}
|
|
}, [useRealApi])
|
|
|
|
const checkApiStatus = useCallback(async () => {
|
|
setIsLoading(true)
|
|
try {
|
|
if (useRealApi) {
|
|
// 检查后端 API 健康状态
|
|
await api.get('/health')
|
|
} else {
|
|
// 检查 Mock API
|
|
await api.get('/photos')
|
|
}
|
|
setIsOnline(true)
|
|
} catch (error) {
|
|
setIsOnline(false)
|
|
console.error('API检查失败:', error)
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}, [useRealApi])
|
|
|
|
useEffect(() => {
|
|
checkApiStatus()
|
|
// 每30秒检查一次API状态
|
|
const interval = setInterval(checkApiStatus, 30000)
|
|
return () => clearInterval(interval)
|
|
}, [useRealApi, checkApiStatus])
|
|
|
|
const toggleApiMode = () => {
|
|
const newMode = !useRealApi
|
|
setUseRealApi(newMode)
|
|
// 在生产环境中,这里应该通过其他方式切换,而不是修改环境变量
|
|
if (typeof window !== 'undefined') {
|
|
localStorage.setItem('useRealApi', newMode.toString())
|
|
}
|
|
}
|
|
|
|
if (process.env.NODE_ENV === 'production') {
|
|
return null // 生产环境不显示此组件
|
|
}
|
|
|
|
return (
|
|
<div className="fixed bottom-4 right-4 z-50 max-w-sm">
|
|
<Alert className="mb-2">
|
|
<Settings className="h-4 w-4" />
|
|
<AlertDescription>
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-2">
|
|
{isLoading ? (
|
|
<RefreshCw className="h-4 w-4 animate-spin" />
|
|
) : isOnline ? (
|
|
<Wifi className="h-4 w-4 text-green-500" />
|
|
) : (
|
|
<WifiOff className="h-4 w-4 text-red-500" />
|
|
)}
|
|
<Badge variant={isOnline ? "default" : "destructive"}>
|
|
{useRealApi ? '后端API' : 'Mock API'}
|
|
</Badge>
|
|
</div>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={toggleApiMode}
|
|
className="ml-2"
|
|
>
|
|
切换
|
|
</Button>
|
|
</div>
|
|
<div className="text-xs text-muted-foreground mt-1">
|
|
{apiUrl}
|
|
</div>
|
|
</AlertDescription>
|
|
</Alert>
|
|
</div>
|
|
)
|
|
} |