|
@@ -4,7 +4,7 @@ from fastapi.templating import Jinja2Templates
|
|
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
|
|
from sqlmodel import Session
|
|
from sqlmodel import Session
|
|
|
from pathlib import Path
|
|
from pathlib import Path
|
|
|
-import uuid, os
|
|
|
|
|
|
|
+import uuid, os, calendar
|
|
|
from datetime import datetime
|
|
from datetime import datetime
|
|
|
|
|
|
|
|
from database import engine, init_db, get_session
|
|
from database import engine, init_db, get_session
|
|
@@ -22,7 +22,7 @@ UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
|
|
COLORS = ["#6366f1","#8b5cf6","#ec4899","#f43f5e","#f97316","#eab308","#22c55e","#14b8a6","#06b6d4","#3b82f6"]
|
|
COLORS = ["#6366f1","#8b5cf6","#ec4899","#f43f5e","#f97316","#eab308","#22c55e","#14b8a6","#06b6d4","#3b82f6"]
|
|
|
|
|
|
|
|
-# ── 首页 ───────────────────────────────────��──────────
|
|
|
|
|
|
|
+# ── 首页 ─────────────────────────────────────────────
|
|
|
@app.get("/", response_class=HTMLResponse)
|
|
@app.get("/", response_class=HTMLResponse)
|
|
|
async def index(request: Request, session: Session = Depends(get_session)):
|
|
async def index(request: Request, session: Session = Depends(get_session)):
|
|
|
subjects = crud.get_all_subjects(session)
|
|
subjects = crud.get_all_subjects(session)
|
|
@@ -71,12 +71,10 @@ async def subject_detail(
|
|
|
if not subject:
|
|
if not subject:
|
|
|
raise HTTPException(404, "科目不存在")
|
|
raise HTTPException(404, "科目不存在")
|
|
|
dates = crud.get_dates_for_subject(session, subject_id)
|
|
dates = crud.get_dates_for_subject(session, subject_id)
|
|
|
|
|
+ today = datetime.now().strftime("%Y-%m-%d")
|
|
|
if not date:
|
|
if not date:
|
|
|
- date = dates[0] if dates else datetime.now().strftime("%Y-%m-%d")
|
|
|
|
|
|
|
+ date = today
|
|
|
images = crud.get_subject_images(session, subject_id, date)
|
|
images = crud.get_subject_images(session, subject_id, date)
|
|
|
- if not images and dates:
|
|
|
|
|
- date = dates[0]
|
|
|
|
|
- images = crud.get_subject_images(session, subject_id, date)
|
|
|
|
|
prev_date = None
|
|
prev_date = None
|
|
|
next_date = None
|
|
next_date = None
|
|
|
if dates and date in dates:
|
|
if dates and date in dates:
|
|
@@ -87,7 +85,7 @@ async def subject_detail(
|
|
|
next_date = dates[idx - 1]
|
|
next_date = dates[idx - 1]
|
|
|
return templates.TemplateResponse("subject_detail.html", {
|
|
return templates.TemplateResponse("subject_detail.html", {
|
|
|
"request": request, "subject": subject, "dates": dates,
|
|
"request": request, "subject": subject, "dates": dates,
|
|
|
- "current_date": date, "images": images,
|
|
|
|
|
|
|
+ "current_date": date, "today": today, "images": images,
|
|
|
"prev_date": prev_date, "next_date": next_date,
|
|
"prev_date": prev_date, "next_date": next_date,
|
|
|
"subjects": crud.get_all_subjects(session)
|
|
"subjects": crud.get_all_subjects(session)
|
|
|
})
|
|
})
|
|
@@ -115,6 +113,21 @@ async def upload_error_images(
|
|
|
crud.add_error_images(session, subject_id, date, filenames, original_names)
|
|
crud.add_error_images(session, subject_id, date, filenames, original_names)
|
|
|
return RedirectResponse(url=f"/subject/{subject_id}?date={date}", status_code=303)
|
|
return RedirectResponse(url=f"/subject/{subject_id}?date={date}", status_code=303)
|
|
|
|
|
|
|
|
|
|
+# ── 录入文字错题 ────────────────────────────────────────
|
|
|
|
|
+@app.post("/subject/{subject_id}/text")
|
|
|
|
|
+async def add_error_text(
|
|
|
|
|
+ subject_id: int,
|
|
|
|
|
+ date: str = Form(...),
|
|
|
|
|
+ question: str = Form(...),
|
|
|
|
|
+ answer: str = Form(default=""),
|
|
|
|
|
+ session: Session = Depends(get_session)
|
|
|
|
|
+):
|
|
|
|
|
+ subject = session.get(Subject, subject_id)
|
|
|
|
|
+ if not subject:
|
|
|
|
|
+ raise HTTPException(404, "科目不存在")
|
|
|
|
|
+ crud.add_error_text(session, subject_id, date, question, answer)
|
|
|
|
|
+ return RedirectResponse(url=f"/subject/{subject_id}?date={date}", status_code=303)
|
|
|
|
|
+
|
|
|
# ── 删除单张图片 ────────────────────────────────────────
|
|
# ── 删除单张图片 ────────────────────────────────────────
|
|
|
@app.post("/image/{image_id}/delete")
|
|
@app.post("/image/{image_id}/delete")
|
|
|
async def delete_image(image_id: int, session: Session = Depends(get_session)):
|
|
async def delete_image(image_id: int, session: Session = Depends(get_session)):
|
|
@@ -150,6 +163,60 @@ async def timeline(request: Request, date: str | None = None, session: Session =
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
# ── 复习汇总 ────────────────────────────────────────────
|
|
# ── 复习汇总 ────────────────────────────────────────────
|
|
|
|
|
+# ── 时间线 New(对比样式) ───────────────────────────
|
|
|
|
|
+@app.get("/timeline-new", response_class=HTMLResponse)
|
|
|
|
|
+async def timeline_new(request: Request, date: str | None = None, session: Session = Depends(get_session)):
|
|
|
|
|
+ subjects = crud.get_all_subjects(session)
|
|
|
|
|
+ all_dates = crud.get_all_dates(session)
|
|
|
|
|
+ if not date:
|
|
|
|
|
+ date = all_dates[0] if all_dates else datetime.now().strftime("%Y-%m-%d")
|
|
|
|
|
+
|
|
|
|
|
+ nodes = []
|
|
|
|
|
+ for d in all_dates:
|
|
|
|
|
+ items = []
|
|
|
|
|
+ for s in subjects:
|
|
|
|
|
+ imgs = crud.get_subject_images(session, s.id, d)
|
|
|
|
|
+ if imgs:
|
|
|
|
|
+ image_items = [img for img in imgs if img.filename]
|
|
|
|
|
+ text_items = [img for img in imgs if not img.filename]
|
|
|
|
|
+ items.append({"subject": s, "images": image_items, "texts": text_items})
|
|
|
|
|
+ if not items:
|
|
|
|
|
+ continue
|
|
|
|
|
+ nodes.append({
|
|
|
|
|
+ "date": d,
|
|
|
|
|
+ "active": d == date,
|
|
|
|
|
+ "total": sum(len(it["images"]) + len(it["texts"]) for it in items),
|
|
|
|
|
+ "image_count": sum(len(it["images"]) for it in items),
|
|
|
|
|
+ "text_count": sum(len(it["texts"]) for it in items),
|
|
|
|
|
+ "subject_count": len(items),
|
|
|
|
|
+ "preview": next((img for it in items for img in it["images"]), None),
|
|
|
|
|
+ "preview_text": next((txt for it in items for txt in it["texts"]), None),
|
|
|
|
|
+ "items": items,
|
|
|
|
|
+ "week": "周" + "一二三四五六日"[calendar.weekday(*[int(x) for x in d.split("-")])],
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ active_node = next((n for n in nodes if n["active"]), None)
|
|
|
|
|
+ total_records = sum(n["total"] for n in nodes)
|
|
|
|
|
+ total_images = sum(n["image_count"] for n in nodes)
|
|
|
|
|
+ total_texts = sum(n["text_count"] for n in nodes)
|
|
|
|
|
+
|
|
|
|
|
+ prev_date = None
|
|
|
|
|
+ next_date = None
|
|
|
|
|
+ if all_dates and date in all_dates:
|
|
|
|
|
+ idx = all_dates.index(date)
|
|
|
|
|
+ if idx < len(all_dates) - 1:
|
|
|
|
|
+ prev_date = all_dates[idx + 1]
|
|
|
|
|
+ if idx > 0:
|
|
|
|
|
+ next_date = all_dates[idx - 1]
|
|
|
|
|
+
|
|
|
|
|
+ return templates.TemplateResponse("timeline_new.html", {
|
|
|
|
|
+ "request": request, "subjects": subjects, "all_dates": all_dates,
|
|
|
|
|
+ "current_date": date, "nodes": nodes,
|
|
|
|
|
+ "active_node": active_node, "total_records": total_records,
|
|
|
|
|
+ "total_images": total_images, "total_texts": total_texts,
|
|
|
|
|
+ "prev_date": prev_date, "next_date": next_date
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
@app.get("/review", response_class=HTMLResponse)
|
|
@app.get("/review", response_class=HTMLResponse)
|
|
|
async def review_page(request: Request, session: Session = Depends(get_session)):
|
|
async def review_page(request: Request, session: Session = Depends(get_session)):
|
|
|
data = crud.get_review_data(session)
|
|
data = crud.get_review_data(session)
|