| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- from fastapi import FastAPI, Request, UploadFile, File, Form, HTTPException, Depends
- from fastapi.staticfiles import StaticFiles
- from fastapi.templating import Jinja2Templates
- from fastapi.responses import HTMLResponse, RedirectResponse
- from sqlmodel import Session
- from pathlib import Path
- import uuid, os
- from datetime import datetime
- from database import engine, init_db, get_session
- from models import Subject, ErrorImage, Evaluation
- import crud
- app = FastAPI(title="满意在线错题本")
- app.mount("/static", StaticFiles(directory="static"), name="static")
- templates = Jinja2Templates(directory="templates")
- init_db()
- UPLOAD_DIR = Path("static/uploads")
- UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
- COLORS = ["#6366f1","#8b5cf6","#ec4899","#f43f5e","#f97316","#eab308","#22c55e","#14b8a6","#06b6d4","#3b82f6"]
- # ── 首页 ───────────────────────────────────��──────────
- @app.get("/", response_class=HTMLResponse)
- async def index(request: Request, session: Session = Depends(get_session)):
- subjects = crud.get_all_subjects(session)
- today = datetime.now().strftime("%Y-%m-%d")
- today_images = {}
- for s in subjects:
- imgs = crud.get_subject_images(session, s.id, date=today)
- if imgs:
- today_images[s.id] = imgs
- evs = crud.get_evaluations(session)
- return templates.TemplateResponse("index.html", {
- "request": request, "subjects": subjects,
- "today": today, "today_images": today_images, "colors": COLORS,
- "evaluations": evs
- })
- # ── 科目管理 ────────────────────────────────────────────
- @app.get("/subjects", response_class=HTMLResponse)
- async def subjects_page(request: Request, session: Session = Depends(get_session)):
- subjects = crud.get_all_subjects(session)
- return templates.TemplateResponse("subjects.html", {"request": request, "subjects": subjects, "colors": COLORS})
- @app.post("/subjects/create")
- async def create_subject(
- request: Request,
- name: str = Form(...),
- color: str = Form(default="#6366f1"),
- session: Session = Depends(get_session)
- ):
- subject = crud.create_subject(session, name.strip(), color)
- return RedirectResponse(url=f"/subject/{subject.id}", status_code=303)
- @app.post("/subjects/{subject_id}/delete")
- async def delete_subject(subject_id: int, session: Session = Depends(get_session)):
- if crud.delete_subject(session, subject_id):
- return RedirectResponse(url="/subjects", status_code=303)
- raise HTTPException(404, "科目不存在")
- # ── 科目详情页 ─────────────────────────────────────────
- @app.get("/subject/{subject_id}", response_class=HTMLResponse)
- async def subject_detail(
- request: Request, subject_id: int, date: str | None = None,
- session: Session = Depends(get_session)
- ):
- subject = session.get(Subject, subject_id)
- if not subject:
- raise HTTPException(404, "科目不存在")
- dates = crud.get_dates_for_subject(session, subject_id)
- if not date:
- date = dates[0] if dates else datetime.now().strftime("%Y-%m-%d")
- 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
- next_date = None
- if dates and date in dates:
- idx = dates.index(date)
- if idx < len(dates) - 1:
- prev_date = dates[idx + 1]
- if idx > 0:
- next_date = dates[idx - 1]
- return templates.TemplateResponse("subject_detail.html", {
- "request": request, "subject": subject, "dates": dates,
- "current_date": date, "images": images,
- "prev_date": prev_date, "next_date": next_date
- })
- # ── 上传错题图片 ────────────────────────────────────────
- @app.post("/subject/{subject_id}/upload")
- async def upload_error_images(
- subject_id: int,
- date: str = Form(...),
- files: list[UploadFile] = File(...),
- session: Session = Depends(get_session)
- ):
- subject = session.get(Subject, subject_id)
- if not subject:
- raise HTTPException(404, "科目不存在")
- filenames, original_names = [], []
- for f in files:
- content = await f.read()
- ext = Path(f.filename).suffix or ".jpg"
- unique_name = f"{uuid.uuid4().hex}{ext}"
- dest = UPLOAD_DIR / unique_name
- dest.write_bytes(content)
- filenames.append(unique_name)
- original_names.append(f.filename)
- crud.add_error_images(session, subject_id, date, filenames, original_names)
- return RedirectResponse(url=f"/subject/{subject_id}?date={date}", status_code=303)
- # ── 删除单张图片 ────────────────────────────────────────
- @app.post("/image/{image_id}/delete")
- async def delete_image(image_id: int, session: Session = Depends(get_session)):
- img = crud.delete_image(session, image_id)
- if not img:
- raise HTTPException(404, "图片不存在")
- return RedirectResponse(url=f"/subject/{img.subject_id}?date={img.date}", status_code=303)
- # ── 时间线 ───────────────────────────────────────────────
- @app.get("/timeline", response_class=HTMLResponse)
- async def timeline(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")
- timeline_data = []
- for s in subjects:
- imgs = crud.get_subject_images(session, s.id, date)
- if imgs:
- timeline_data.append({"subject": s, "images": imgs})
- 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.html", {
- "request": request, "subjects": subjects, "all_dates": all_dates,
- "current_date": date, "timeline_data": timeline_data,
- "prev_date": prev_date, "next_date": next_date
- })
- # ── 复习汇总 ────────────────────────────────────────────
- @app.get("/review", response_class=HTMLResponse)
- async def review_page(request: Request, session: Session = Depends(get_session)):
- data = crud.get_review_data(session)
- subjects = crud.get_all_subjects(session)
- return templates.TemplateResponse("review.html", {
- "request": request, "review_data": data, "subjects": subjects
- })
- # ── 知识评价 ────────────────────────────────────────────
- @app.get("/evaluations", response_class=HTMLResponse)
- async def evaluations_page(request: Request, subject_id: int | None = None, session: Session = Depends(get_session)):
- subjects = crud.get_all_subjects(session)
- evs = crud.get_evaluations(session, subject_id)
- return templates.TemplateResponse("evaluations.html", {
- "request": request, "subjects": subjects, "evaluations": evs,
- "current_subject": subject_id, "today": datetime.now().strftime("%Y-%m-%d")
- })
- @app.post("/evaluations/add")
- async def add_evaluation(
- subject_id: int = Form(...),
- date: str = Form(...),
- title: str = Form(...),
- content: str = Form(default=""),
- file: UploadFile | None = File(None),
- session: Session = Depends(get_session)
- ):
- filename = None
- if file and file.filename:
- ext = Path(file.filename).suffix or ".pdf"
- unique_name = f"eval_{uuid.uuid4().hex}{ext}"
- dest = UPLOAD_DIR / unique_name
- dest.write_bytes(await file.read())
- filename = unique_name
- crud.add_evaluation(session, subject_id, date, title, content, filename)
- return RedirectResponse(url=f"/evaluations?subject_id={subject_id}", status_code=303)
- @app.post("/evaluation/{eval_id}/delete")
- async def delete_evaluation(eval_id: int, session: Session = Depends(get_session)):
- ev = session.get(Evaluation, eval_id)
- if not ev:
- raise HTTPException(404)
- if ev.filename:
- p = UPLOAD_DIR / ev.filename
- if p.exists():
- p.unlink()
- session.delete(ev)
- session.commit()
- return RedirectResponse(url=f"/evaluations?subject_id={ev.subject_id}", status_code=303)
- if __name__ == "__main__":
- import uvicorn
- uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)
|