feat: 全面优化前端架构

- 本地化所有资源 (Vue3, Element Plus) 无需外部 CDN
- 重新设计主页:登录页 + 系统概览 + 侧边栏导航
- 完善管理后台功能:选题管理、系统日志、用户管理
- 全面支持 PC 和 H5 移动端(响应式布局 + 底部导航)
- 优化后端 API 返回格式,兼容前端需求
- 默认首页为登录/系统概览页面
- 统计卡片可点击筛选选题列表
- 模块状态实时展示
This commit is contained in:
lt
2026-04-27 15:14:26 +08:00
parent 1eef4c177b
commit 5fe426fc90
16 changed files with 21410 additions and 615 deletions
+36 -80
View File
@@ -22,89 +22,69 @@ DATA_DIR = PROJECT_ROOT / "automation" / "data"
router = APIRouter(prefix="/api/system", tags=["system"])
@router.get("/status", response_model=SystemStatus)
@router.get("/status")
def get_status(db: Session = Depends(get_db)):
"""系统状态概览"""
"""系统状态概览 - 返回前端兼容格式"""
total = db.query(Topic).count()
by_status_result = db.query(Topic.status, func.count()).group_by(Topic.status).all()
by_status = {status: count for status, count in by_status_result}
# 确保返回所有状态,避免前端 undefined
for key in ('pending', 'ready', 'published'):
by_status.setdefault(key, 0)
ready = db.query(Topic).filter(Topic.status == "ready").all()
# 确保返回所有状态
status_map = {
'待处理': by_status.get('pending', 0),
'待审查': by_status.get('review', 0),
'待发布': by_status.get('ready', 0),
'已发布': by_status.get('published', 0)
}
today_str = date.today().isoformat()
# 计算今日文章数:查找 releases/2026-04-16 目录下的 html 文件
# 这里简单统计数据库中 created_at 为今天的文章(不完全准确)
today_articles = db.query(Article).filter(
func.date(Article.created_at) == date.today()
# 计算今日新增
today = date.today()
today_count = db.query(Topic).filter(
func.date(Topic.created_at) == today
).count()
# 合规率:假设所有 ready 的都是合规的(实际从report读取)
# 可以后续优化
# 获取最后一次优化时间
last_opt = db.query(Article).filter(
Article.status == "optimized"
).order_by(Article.created_at.desc()).first()
return SystemStatus(
total_topics=total,
topics_by_status=by_status,
ready_topics=ready,
today_articles=today_articles,
compliance_rate=100.0, # placeholder
last_optimization=last_opt.created_at if last_opt else None
)
return {
"stats": {
"total": total,
"pending": status_map['待处理'],
"review": status_map['待审查'],
"ready": status_map['待发布'],
"published": status_map['已发布'],
"today": today_count
}
}
@router.post("/generate/run", dependencies=[Depends(get_current_user)])
def trigger_generation(topic_id: str = None, db: Session = Depends(get_db)):
"""手动触发内容创作任务
Args:
topic_id: 可选,指定要创作的选题ID。不指定则创作优先级最高的待处理选题。
"""
"""手动触发内容创作任务"""
try:
result = run_creator(topic_id)
if not result["ok"]:
raise HTTPException(status_code=500, detail=result["error"])
from ..core.sync import sync_all_topics
sync_all_topics()
return {"message": "Generation triggered", "result": result}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/optimize/run", dependencies=[Depends(get_current_user)])
def trigger_optimization(topic_ids: List[str] = None, db: Session = Depends(get_db)):
"""手动触发合规优化任务
Args:
topic_ids: 可选,指定要优化的选题ID列表。不指定则优化所有 draft 状态文章。
"""
"""手动触发合规优化任务"""
try:
result = run_optimizer(topic_ids)
if not result["ok"]:
raise HTTPException(status_code=500, detail=result["error"])
report = result.get("report")
if report:
from ..core.sync import sync_all_topics
sync_all_topics()
return {
"message": "Optimization completed",
"summary": report["summary"]
}
return {"message": "Optimization completed", "summary": report["summary"]}
else:
return {"message": "Optimization completed but no report found", "stdout": result.get("stdout", "")}
return {"message": "Optimization completed", "stdout": result.get("stdout", "")}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/logs/{log_date}", dependencies=[Depends(get_current_user)])
def get_logs(log_date: str, log_type: str = "creator"):
"""读取日志文件内容log_type: creator, optimizer, collector"""
"""读取日志文件内容"""
log_file = LOGS_DIR / f"{log_type}_{log_date}.log"
if not log_file.exists():
raise HTTPException(status_code=404, detail=f"Log file not found: {log_file}")
@@ -114,56 +94,35 @@ def get_logs(log_date: str, log_type: str = "creator"):
@router.get("/pipeline/status", dependencies=[Depends(get_current_user)])
def get_pipeline_status():
"""获取流水线各模块状态(最后运行时间和结果)"""
"""获取流水线各模块状态"""
try:
# 读取选题文件
topics_file = DATA_DIR / "sustainability_topics.json"
topics = []
if topics_file.exists():
topics = json.loads(topics_file.read_text(encoding='utf-8'))
# 统计状态分布
status_counts = {}
for t in topics:
s = t.get('status', 'unknown')
status_counts[s] = status_counts.get(s, 0) + 1
# 检查各日志文件的最新修改时间
log_files = {
"collector": LOGS_DIR / f"collector_{date.today().isoformat()}.log",
"creator": LOGS_DIR / f"creator_{date.today().isoformat()}.log",
"optimizer": LOGS_DIR / f"optimizer_{date.today().isoformat()}.log",
}
pipeline_status = {}
for name, log_file in log_files.items():
if log_file.exists():
mtime = datetime.fromtimestamp(log_file.stat().st_mtime)
pipeline_status[name] = {
"last_run": mtime.isoformat(),
"exists": True,
"size_bytes": log_file.stat().st_size
}
# 简单推断成功/失败(TODO: 解析日志加强)
last_lines = log_file.read_text(encoding='utf-8').splitlines()[-10:]
has_error = any("error" in line.lower() or "失败" in line or "failed" in line.lower() for line in last_lines)
pipeline_status[name]["has_error"] = has_error
pipeline_status[name] = {"last_run": mtime.isoformat(), "exists": True}
else:
pipeline_status[name] = {"exists": False, "last_run": None}
return {
"topics_count": len(topics),
"status_distribution": status_counts,
"pipeline_modules": pipeline_status,
"data_dir": str(DATA_DIR),
"logs_dir": str(LOGS_DIR)
}
return {"topics_count": len(topics), "status_distribution": status_counts, "pipeline_modules": pipeline_status}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/sync/run")
def run_sync():
"""手动触发数据同步(流水线JSON → 平台数据库)"""
"""手动触发数据同步"""
try:
sync_all_topics()
return {"message": "Sync completed"}
@@ -172,16 +131,13 @@ def run_sync():
@router.get("/automation/topics")
def list_automation_topics():
"""直接读取自动化流水线的选题JSON(供调试)"""
"""直接读取自动化流水线的选题 JSON"""
try:
topics_file = DATA_DIR / "sustainability_topics.json"
if not topics_file.exists():
raise HTTPException(status_code=404, detail="Topics JSON not found")
topics = json.loads(topics_file.read_text(encoding='utf-8'))
return {
"count": len(topics),
"topics": topics[-50:] # 只返回最近50个,避免过大
}
return {"count": len(topics), "topics": topics[-50:]}
except json.JSONDecodeError as e:
raise HTTPException(status_code=500, detail=f"JSON parse error: {e}")
except Exception as e:
@@ -189,9 +145,9 @@ def list_automation_topics():
@router.post("/refresh")
def refresh_all():
"""刷新所有数据:同步JSON + 更新状态"""
"""刷新所有数据"""
try:
sync_all_topics()
return {"message": "Refresh completed"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
raise HTTPException(status_code=500, detail=str(e))