修正首页的内容
This commit is contained in:
parent
a6f2895dad
commit
4ea47541e9
@ -309,10 +309,13 @@ async def get_available_sources(market: str):
|
|||||||
|
|
||||||
@router.get("/recent", response_model=List[dict])
|
@router.get("/recent", response_model=List[dict])
|
||||||
async def get_recent_companies(
|
async def get_recent_companies(
|
||||||
data_source: str = "Bloomberg",
|
data_source: str = None,
|
||||||
db: AsyncSession = Depends(get_db)
|
db: AsyncSession = Depends(get_db)
|
||||||
):
|
):
|
||||||
"""获取最近更新的公司列表"""
|
"""获取最近更新的公司列表
|
||||||
|
|
||||||
|
如果不指定 data_source,将返回所有数据源的最近更新。
|
||||||
|
"""
|
||||||
return await data_fetcher_service.get_recent_companies(
|
return await data_fetcher_service.get_recent_companies(
|
||||||
data_source=data_source,
|
data_source=data_source,
|
||||||
db=db,
|
db=db,
|
||||||
|
|||||||
@ -492,24 +492,28 @@ def get_available_data_sources(market: str) -> List[Dict]:
|
|||||||
return sources
|
return sources
|
||||||
|
|
||||||
async def get_recent_companies(
|
async def get_recent_companies(
|
||||||
data_source: str,
|
data_source: Optional[str],
|
||||||
db: AsyncSession,
|
db: AsyncSession,
|
||||||
limit: int = 20
|
limit: int = 20
|
||||||
) -> List[Dict]:
|
) -> List[Dict]:
|
||||||
"""获取指定数据源下最近更新的公司"""
|
"""获取最近更新的公司列表
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data_source: 可选的数据源过滤。如果为None,则返回所有数据源的最近更新。
|
||||||
|
db: 数据库会话
|
||||||
|
limit: 返回数量限制
|
||||||
|
"""
|
||||||
query = (
|
query = (
|
||||||
select(Company, DataUpdate.completed_at)
|
select(Company, DataUpdate.completed_at, DataUpdate.data_source)
|
||||||
.join(DataUpdate, Company.id == DataUpdate.company_id)
|
.join(DataUpdate, Company.id == DataUpdate.company_id)
|
||||||
.where(
|
.where(DataUpdate.status == 'completed')
|
||||||
and_(
|
|
||||||
DataUpdate.data_source == data_source,
|
|
||||||
DataUpdate.status == 'completed'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.order_by(DataUpdate.completed_at.desc())
|
.order_by(DataUpdate.completed_at.desc())
|
||||||
.limit(limit * 3) # Fetch more to handle duplicates
|
.limit(limit * 5) # Fetch more to handle duplicates
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if data_source:
|
||||||
|
query = query.where(DataUpdate.data_source == data_source)
|
||||||
|
|
||||||
result = await db.execute(query)
|
result = await db.execute(query)
|
||||||
rows = result.all()
|
rows = result.all()
|
||||||
|
|
||||||
@ -517,13 +521,14 @@ async def get_recent_companies(
|
|||||||
companies = []
|
companies = []
|
||||||
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
company, completed_at = row
|
company, completed_at, source = row
|
||||||
if company.id not in seen_ids:
|
if company.id not in seen_ids:
|
||||||
seen_ids.add(company.id)
|
seen_ids.add(company.id)
|
||||||
companies.append({
|
companies.append({
|
||||||
"market": company.market,
|
"market": company.market,
|
||||||
"symbol": company.symbol,
|
"symbol": company.symbol,
|
||||||
"company_name": company.company_name,
|
"company_name": company.company_name,
|
||||||
|
"data_source": source,
|
||||||
"last_update": completed_at.strftime('%Y-%m-%d %H:%M') if completed_at else ""
|
"last_update": completed_at.strftime('%Y-%m-%d %H:%M') if completed_at else ""
|
||||||
})
|
})
|
||||||
if len(companies) >= limit:
|
if len(companies) >= limit:
|
||||||
|
|||||||
@ -116,7 +116,7 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 历史记录 */}
|
{/* 历史记录 */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="w-full">
|
||||||
<HistoryList onSelect={handleCompanySelect} />
|
<HistoryList onSelect={handleCompanySelect} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { getReports } from "@/lib/api"
|
import { getRecentCompanies } from "@/lib/api"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
||||||
import { Loader2 } from "lucide-react"
|
import { Loader2, Database } from "lucide-react"
|
||||||
import type { SearchResult } from "@/lib/types"
|
import type { SearchResult } from "@/lib/types"
|
||||||
|
|
||||||
interface HistoryListProps {
|
interface HistoryListProps {
|
||||||
@ -12,59 +10,50 @@ interface HistoryListProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function HistoryList({ onSelect }: HistoryListProps) {
|
export function HistoryList({ onSelect }: HistoryListProps) {
|
||||||
const [reports, setReports] = useState<any[]>([])
|
const [companies, setCompanies] = useState<any[]>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getReports().then(setReports).catch(console.error).finally(() => setLoading(false))
|
getRecentCompanies().then(setCompanies).catch(console.error).finally(() => setLoading(false))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (loading) return <Loader2 className="animate-spin text-muted-foreground" />
|
if (loading) return <Loader2 className="animate-spin text-muted-foreground" />
|
||||||
|
|
||||||
if (reports.length === 0) return <div className="text-muted-foreground">没有找到历史记录。</div>
|
if (companies.length === 0) return <div className="text-muted-foreground">没有找到最近的数据记录。</div>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
<div className="grid gap-4 grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
|
||||||
{reports.map((report: any) => (
|
{companies.map((company: any, index: number) => (
|
||||||
<Card
|
<Card
|
||||||
key={report.id}
|
key={`${company.symbol}-${index}`}
|
||||||
className="hover:shadow-lg transition-shadow cursor-pointer"
|
className="hover:shadow-lg transition-shadow cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (onSelect) {
|
if (onSelect) {
|
||||||
onSelect({
|
onSelect({
|
||||||
symbol: report.symbol,
|
symbol: company.symbol,
|
||||||
market: report.market,
|
market: company.market,
|
||||||
company_name: report.company_name
|
company_name: company.company_name
|
||||||
}, report.data_source)
|
}, company.data_source)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CardHeader>
|
<CardHeader className="pb-2">
|
||||||
<CardTitle className="text-lg">{report.company_name}</CardTitle>
|
<div className="flex justify-between items-start">
|
||||||
|
<CardTitle className="text-lg">{company.company_name}</CardTitle>
|
||||||
|
<Badge variant="outline">{company.market}</Badge>
|
||||||
|
</div>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
{report.market} {report.symbol}
|
{company.symbol}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between text-sm text-muted-foreground">
|
||||||
<span className="text-sm text-muted-foreground">
|
<span>{company.last_update}</span>
|
||||||
{new Date(report.created_at).toLocaleString('zh-CN')}
|
<div className="flex items-center gap-1">
|
||||||
</span>
|
<Database className="w-3 h-3" />
|
||||||
<Badge variant={
|
<span>{company.data_source}</span>
|
||||||
report.status === "completed" ? "default" :
|
|
||||||
report.status === "in_progress" ? "secondary" :
|
|
||||||
report.status === "failed" ? "destructive" : "outline"
|
|
||||||
}>
|
|
||||||
{report.status === "completed" ? "已完成" :
|
|
||||||
report.status === "in_progress" ? "进行中" :
|
|
||||||
report.status === "failed" ? "失败" : "待处理"}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
{report.ai_model && (
|
|
||||||
<div className="mt-2 text-xs text-muted-foreground">
|
|
||||||
模型: {report.ai_model}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -163,6 +163,19 @@ export async function getAnalysisHistory(
|
|||||||
return res.json()
|
return res.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取最近更新的公司列表
|
||||||
|
*/
|
||||||
|
export async function getRecentCompanies(dataSource?: string): Promise<any[]> {
|
||||||
|
let url = `${API_BASE}/data/recent`
|
||||||
|
if (dataSource) {
|
||||||
|
url += `?data_source=${dataSource}`
|
||||||
|
}
|
||||||
|
const res = await fetch(url)
|
||||||
|
if (!res.ok) throw new Error("Failed to fetch recent companies")
|
||||||
|
return res.json()
|
||||||
|
}
|
||||||
|
|
||||||
// ========== 兼容性函数 ==========
|
// ========== 兼容性函数 ==========
|
||||||
/**
|
/**
|
||||||
* 获取所有分析报告(兼容旧API)
|
* 获取所有分析报告(兼容旧API)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user