diff --git a/backend/app/__pycache__/main.cpython-312.pyc b/backend/app/__pycache__/main.cpython-312.pyc index c3dc46f..41e088f 100644 Binary files a/backend/app/__pycache__/main.cpython-312.pyc and b/backend/app/__pycache__/main.cpython-312.pyc differ diff --git a/backend/app/main.py b/backend/app/main.py index 5389069..0c9037e 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -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目录存在 diff --git a/backend/app/routers/__pycache__/stats.cpython-312.pyc b/backend/app/routers/__pycache__/stats.cpython-312.pyc new file mode 100644 index 0000000..582021a Binary files /dev/null and b/backend/app/routers/__pycache__/stats.cpython-312.pyc differ diff --git a/backend/app/routers/stats.py b/backend/app/routers/stats.py new file mode 100644 index 0000000..af29a6e --- /dev/null +++ b/backend/app/routers/stats.py @@ -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 + } diff --git a/backend/logs/app_2026-01-23.log b/backend/logs/app_2026-01-23.log index 98f35ef..1311ac6 100644 --- a/backend/logs/app_2026-01-23.log +++ b/backend/logs/app_2026-01-23.log @@ -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小时 diff --git a/frontend/js/app.js b/frontend/js/app.js index 3253a22..446b0a4 100644 --- a/frontend/js/app.js +++ b/frontend/js/app.js @@ -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 = ` -
${report.company_name} - ${report.title}
-
- AI分析已完成 • ${new Date(report.created_at).toLocaleDateString()} -
- - `; - insightsContainer.appendChild(el); - } - }); - - if (insightsContainer.children.length === 0) { - insightsContainer.innerHTML = '
暂无分析结果
'; + 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 = ` +
${report.title}
+
+ AI分析已完成 • ${report.date} +
+ + `; + insightsContainer.appendChild(el); + } else { + insightsContainer.innerHTML = '
暂无分析结果
'; } // 渲染日志