feat: Add dashboard statistics endpoint and initial frontend application for managing companies and reports.
This commit is contained in:
parent
200178ebcc
commit
79d6cedaf1
Binary file not shown.
@ -9,7 +9,7 @@ from contextlib import asynccontextmanager
|
||||
|
||||
from app.config import settings
|
||||
from app.database import init_db
|
||||
from app.routers import companies, reports, analysis, scheduler
|
||||
from app.routers import companies, reports, analysis, scheduler, stats
|
||||
from app.services.scheduler_service import scheduler_service
|
||||
from app.utils.logger import logger
|
||||
|
||||
@ -55,6 +55,7 @@ app.include_router(companies.router)
|
||||
app.include_router(reports.router)
|
||||
app.include_router(analysis.router)
|
||||
app.include_router(scheduler.router)
|
||||
app.include_router(stats.router)
|
||||
|
||||
# 挂载静态文件 (前端)
|
||||
# 确保frontend目录存在
|
||||
|
||||
BIN
backend/app/routers/__pycache__/stats.cpython-312.pyc
Normal file
BIN
backend/app/routers/__pycache__/stats.cpython-312.pyc
Normal file
Binary file not shown.
42
backend/app/routers/stats.py
Normal file
42
backend/app/routers/stats.py
Normal file
@ -0,0 +1,42 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, func, desc
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from app.database import get_db
|
||||
from app.models import Company, Report, AnalysisResult
|
||||
|
||||
router = APIRouter(prefix="/api/stats", tags=["统计数据"])
|
||||
|
||||
@router.get("/dashboard")
|
||||
async def get_dashboard_stats(db: AsyncSession = Depends(get_db)):
|
||||
"""获取仪表盘统计数据"""
|
||||
|
||||
# 统计公司数
|
||||
company_count = await db.scalar(select(func.count(Company.id)))
|
||||
|
||||
# 统计报告数
|
||||
report_count = await db.scalar(select(func.count(Report.id)))
|
||||
|
||||
# 统计已分析数
|
||||
analyzed_count = await db.scalar(select(func.count(Report.id)).where(Report.is_analyzed == True))
|
||||
|
||||
# 获取最新的一条分析完成的报告
|
||||
stmt = select(Report).where(Report.is_analyzed == True).order_by(desc(Report.updated_at)).limit(1).options(selectinload(Report.company))
|
||||
result = await db.execute(stmt)
|
||||
latest_report = result.scalar_one_or_none()
|
||||
|
||||
latest_insight = None
|
||||
if latest_report:
|
||||
latest_insight = {
|
||||
"id": latest_report.id,
|
||||
"title": f"{latest_report.company.short_name or latest_report.company.company_name} - {latest_report.title}",
|
||||
"date": latest_report.updated_at.strftime("%Y/%m/%d")
|
||||
}
|
||||
|
||||
return {
|
||||
"company_count": company_count or 0,
|
||||
"report_count": report_count or 0,
|
||||
"analyzed_count": analyzed_count or 0,
|
||||
"latest_insight": latest_insight
|
||||
}
|
||||
@ -1 +1,7 @@
|
||||
2026-01-23 00:00:32 | WARNING | app.services.ai_analyzer:call_ai:77 - AI调用异常: Server disconnected without sending a response.,2秒后重试...
|
||||
2026-01-23 00:11:08 | INFO | app.main:lifespan:34 - 应用正在关闭...
|
||||
2026-01-23 00:11:08 | INFO | app.services.scheduler_service:stop:49 - 定时任务调度器已停止
|
||||
2026-01-23 00:11:25 | INFO | app.main:lifespan:21 - 正在启动 眼镜行业情报分析系统...
|
||||
2026-01-23 00:11:39 | INFO | app.main:lifespan:21 - 正在启动 眼镜行业情报分析系统...
|
||||
2026-01-23 00:11:39 | INFO | app.main:lifespan:25 - 数据库已初始化
|
||||
2026-01-23 00:11:39 | INFO | app.services.scheduler_service:start:42 - 定时任务调度器已启动,间隔: 24小时
|
||||
|
||||
@ -252,44 +252,40 @@ const app = {
|
||||
// 获取仪表盘数据
|
||||
async fetchDashboard() {
|
||||
try {
|
||||
// 获取统计数据
|
||||
const [companies, reports, logs] = await Promise.all([
|
||||
fetch(`${API_BASE}/companies`).then(r => r.json()),
|
||||
fetch(`${API_BASE}/reports?page_size=5`).then(r => r.json()),
|
||||
// 获取统计数据和日志
|
||||
const [stats, logs] = await Promise.all([
|
||||
fetch(`${API_BASE}/stats/dashboard`).then(r => r.json()),
|
||||
fetch(`${API_BASE}/scheduler/logs`).then(r => r.json())
|
||||
]);
|
||||
|
||||
// 更新计数
|
||||
document.getElementById('stat-companies').textContent = companies.length;
|
||||
document.getElementById('stat-reports').textContent = reports.length; // 实际应该请求总数API
|
||||
|
||||
// 简单估算已分析数(实际应从API获取)
|
||||
const analyzedCount = reports.filter(r => r.is_analyzed).length;
|
||||
document.getElementById('stat-analyzed').textContent = analyzedCount;
|
||||
document.getElementById('stat-companies').textContent = stats.company_count;
|
||||
document.getElementById('stat-reports').textContent = stats.report_count;
|
||||
document.getElementById('stat-analyzed').textContent = stats.analyzed_count;
|
||||
|
||||
// 渲染最新洞察
|
||||
const insightsContainer = document.getElementById('recent-insights-list');
|
||||
insightsContainer.innerHTML = '';
|
||||
|
||||
reports.slice(0, 5).forEach(report => {
|
||||
if (report.is_analyzed) {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'insight-item card';
|
||||
el.style.marginBottom = '10px';
|
||||
el.style.padding = '12px';
|
||||
el.innerHTML = `
|
||||
<div style="font-weight:600;font-size:0.9rem">${report.company_name} - ${report.title}</div>
|
||||
<div style="font-size:0.8rem;color:#64748b;margin-top:4px">
|
||||
AI分析已完成 • ${new Date(report.created_at).toLocaleDateString()}
|
||||
</div>
|
||||
<button class="btn-xs" style="margin-top:8px" onclick="app.showReportDetail(${report.id})">查看详情</button>
|
||||
`;
|
||||
insightsContainer.appendChild(el);
|
||||
}
|
||||
});
|
||||
|
||||
if (insightsContainer.children.length === 0) {
|
||||
insightsContainer.innerHTML = '<div style="color:#aaa;text-align:center;padding:20px">暂无分析结果</div>';
|
||||
if (stats.latest_insight) {
|
||||
const report = stats.latest_insight;
|
||||
const el = document.createElement('div');
|
||||
el.className = 'insight-item card';
|
||||
el.style.marginBottom = '10px';
|
||||
el.style.padding = '16px';
|
||||
el.style.borderLeft = '4px solid #3b82f6';
|
||||
el.innerHTML = `
|
||||
<div style="font-weight:600;font-size:1rem;margin-bottom:8px">${report.title}</div>
|
||||
<div style="font-size:0.85rem;color:#64748b;margin-bottom:12px">
|
||||
<i class="fas fa-check-circle" style="color:#10b981"></i> AI分析已完成 • ${report.date}
|
||||
</div>
|
||||
<button class="btn-xs" onclick="app.showReportDetail(${report.id}); return false;">
|
||||
<i class="fas fa-eye"></i> 查看详情
|
||||
</button>
|
||||
`;
|
||||
insightsContainer.appendChild(el);
|
||||
} else {
|
||||
insightsContainer.innerHTML = '<div style="color:#aaa;text-align:center;padding:20px;border:1px dashed #eee;border-radius:8px">暂无分析结果</div>';
|
||||
}
|
||||
|
||||
// 渲染日志
|
||||
|
||||
Loading…
Reference in New Issue
Block a user