Files
yu-zhi-ran/platform/backend/app/api/topics.py
T
lt 277b13eaae feat: 完成布局优化 - 操作列固定、批量按钮自适应、分类标签带数量
优化内容:
1. 表格布局:
   - 使用 calc(100vw - 160px) 确保表格不超出视口
   - 操作列 fixed='right' 固定在右侧,宽度 300px
   - 按钮 3 个后自动换行 (max-width: 200px)
   - 恢复合理列宽,不再过度压缩

2. 批量操作区域:
   - 容器改为 inline-block,宽度自适应按钮内容
   - 背景宽度与按钮总宽度匹配

3. 分类标签:
   - 显示数量 (如 '待处理 (20)')
   - 点击切换筛选,去掉误导的 'X' 图标

4. 删除功能:
   - 操作列增加删除按钮
   - 删除前弹出确认对话框

5. 系统日志:
   - 修复后端日志路径 (parents[4])
   - 404 时显示友好提示

6. 其他:
   - 左侧菜单宽度 160px
   - 所有功能保留 (登录、用户管理、批量操作等)
2026-04-27 11:32:17 +08:00

199 lines
7.0 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import List, Optional
from datetime import datetime, date
from pathlib import Path
from ..database import get_db
from ..models import Topic, PublishRecord
from ..schemas import TopicResponse, PublishRequest, PublishActionRequest, PublishRecordResponse
from .auth import get_current_user
router = APIRouter(prefix="/api/topics", tags=["topics"], dependencies=[Depends(get_current_user)])
PROJECT_ROOT = Path(__file__).parent.parent.parent
@router.get("", response_model=List[TopicResponse])
def list_topics(
status: str = None,
db: Session = Depends(get_db)
):
# 确保读取最新数据,清除会话缓存
db.expire_all()
query = db.query(Topic)
if status:
query = query.filter(Topic.status == status)
topics = query.order_by(Topic.priority_score.desc(), Topic.created_at.desc()).all()
return topics
@router.get("/{topic_id}", response_model=TopicResponse)
def get_topic(topic_id: str, db: Session = Depends(get_db)):
topic = db.query(Topic).filter(Topic.id == topic_id).first()
if not topic:
raise HTTPException(status_code=404, detail="Topic not found")
return topic
@router.post("/{topic_id}/publish")
def publish_topic(topic_id: str, req: PublishRequest, db: Session = Depends(get_db)):
topic = db.query(Topic).filter(Topic.id == topic_id).first()
if not topic:
raise HTTPException(status_code=404, detail="Topic not found")
if topic.status != "ready":
raise HTTPException(status_code=400, detail="Topic not in ready status")
# 更新选题状态
topic.status = "published"
topic.published_at = datetime.now().date()
topic.updated_at = datetime.now()
topic.platform_urls = req.platform_urls
db.commit()
# 创建发布记录
record = PublishRecord(
topic_id=topic_id,
platform=req.platform,
action="publish",
status="success",
operator=req.operator,
description=req.description,
suggestion=req.suggestion,
url=req.url,
error_msg=req.error_msg
)
db.add(record)
db.commit()
db.refresh(record)
return {"message": "Topic marked as published", "topic_id": topic_id, "record_id": record.id}
# 获取选题的发布记录
@router.get("/{topic_id}/publish-records", response_model=List[PublishRecordResponse])
def get_publish_records(topic_id: str, db: Session = Depends(get_db)):
records = db.query(PublishRecord).filter(PublishRecord.topic_id == topic_id).order_by(PublishRecord.created_at.desc()).all()
return records
# 创建新的发布记录(用于手动记录发布情况)
@router.post("/{topic_id}/publish-records")
def create_publish_record(topic_id: str, req: PublishActionRequest, db: Session = Depends(get_db)):
# 验证选题存在
topic = db.query(Topic).filter(Topic.id == topic_id).first()
if not topic:
raise HTTPException(status_code=404, detail="Topic not found")
record = PublishRecord(
topic_id=topic_id,
platform=req.platform or "unknown",
action=req.action,
status=req.status,
operator=req.operator,
description=req.description,
suggestion=req.suggestion,
url=req.url,
error_msg=req.error_msg
)
db.add(record)
db.commit()
db.refresh(record)
# 如果操作是发布成功,且platform指定,则更新topic的platform_urls
if req.action == "publish" and req.status == "success" and req.platform and req.url:
if not topic.platform_urls:
topic.platform_urls = {}
topic.platform_urls[req.platform] = req.url
db.commit()
return record
# 更新发布记录
@router.put("/publish-records/{record_id}")
def update_publish_record(record_id: int, req: PublishActionRequest, db: Session = Depends(get_db)):
record = db.query(PublishRecord).filter(PublishRecord.id == record_id).first()
if not record:
raise HTTPException(status_code=404, detail="Record not found")
# 更新字段
for field, value in req.dict(exclude_unset=True).items():
setattr(record, field, value)
record.updated_at = datetime.now()
db.commit()
return record
@router.get("/{topic_id}/preview")
def preview_topic(topic_id: str, platform: str = Query("zhihu", regex="^(zhihu|wechat|xiaohongshu)$")):
"""
预览某选题在指定平台的HTML内容。
查找最近发布的release文件。
"""
# 查找最近的发布包
releases_dir = PROJECT_ROOT / "automation" / "data" / "releases"
if not releases_dir.exists():
raise HTTPException(status_code=404, detail="No releases found")
# 按日期倒序查找
dates = sorted([d.name for d in releases_dir.iterdir() if d.is_dir()], reverse=True)
found = None
for dt in dates:
file_path = releases_dir / dt / platform / f"{platform}_{topic_id}_{platform}.html"
if file_path.exists():
found = file_path
break
if not found:
raise HTTPException(status_code=404, detail=f"Preview not found for topic {topic_id} on {platform}")
content = found.read_text(encoding="utf-8")
return {"topic_id": topic_id, "platform": platform, "html": content}
@router.get("/{topic_id}/packages")
def list_packages(topic_id: str):
"""
列出某选题的所有发布包(HTML文件)。
"""
releases_dir = PROJECT_ROOT / "automation" / "data" / "releases"
if not releases_dir.exists():
return {"packages": []}
packages = []
dates = sorted([d.name for d in releases_dir.iterdir() if d.is_dir()], reverse=True)
for dt in dates:
date_dir = releases_dir / dt
for platform in ["zhihu", "wechat", "xiaohongshu"]:
file_path = date_dir / platform / f"{platform}_{topic_id}_{platform}.html"
if file_path.exists():
stat = file_path.stat()
packages.append({
"platform": platform,
"path": str(file_path.relative_to(PROJECT_ROOT)),
"size": stat.st_size,
"modified": datetime.fromtimestamp(stat.st_mtime).isoformat()
})
return {"packages": packages}
@router.post("/{topic_id}/packages/generate")
def generate_packages(topic_id: str):
"""
手动触发单个选题的发布包生成。
相当于执行 publisher.py 针对单个选题。
"""
# TODO: 实际调用 publisher.py 逻辑,这里先返回模拟响应
return {"message": "Package generation triggered", "topic_id": topic_id, "status": "pending"}
@router.delete("/{topic_id}")
def delete_topic(topic_id: str, db: Session = Depends(get_db)):
"""删除选题"""
topic = db.query(Topic).filter(Topic.id == topic_id).first()
if not topic:
raise HTTPException(status_code=404, detail="选题不存在")
db.delete(topic)
db.commit()
return {"message": "删除成功", "topic_id": topic_id}