app.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. from fastapi import FastAPI, Request, UploadFile, File, Form, HTTPException, Depends
  2. from fastapi.staticfiles import StaticFiles
  3. from fastapi.templating import Jinja2Templates
  4. from fastapi.responses import HTMLResponse, RedirectResponse
  5. from sqlmodel import Session
  6. from pathlib import Path
  7. import uuid, os
  8. from datetime import datetime
  9. from database import engine, init_db, get_session
  10. from models import Subject, ErrorImage, Evaluation
  11. import crud
  12. app = FastAPI(title="满意在线错题本")
  13. app.mount("/static", StaticFiles(directory="static"), name="static")
  14. templates = Jinja2Templates(directory="templates")
  15. init_db()
  16. UPLOAD_DIR = Path("static/uploads")
  17. UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
  18. COLORS = ["#6366f1","#8b5cf6","#ec4899","#f43f5e","#f97316","#eab308","#22c55e","#14b8a6","#06b6d4","#3b82f6"]
  19. # ── 首页 ───────────────────────────────────��──────────
  20. @app.get("/", response_class=HTMLResponse)
  21. async def index(request: Request, session: Session = Depends(get_session)):
  22. subjects = crud.get_all_subjects(session)
  23. today = datetime.now().strftime("%Y-%m-%d")
  24. today_images = {}
  25. for s in subjects:
  26. imgs = crud.get_subject_images(session, s.id, date=today)
  27. if imgs:
  28. today_images[s.id] = imgs
  29. evs = crud.get_evaluations(session)
  30. return templates.TemplateResponse("index.html", {
  31. "request": request, "subjects": subjects,
  32. "today": today, "today_images": today_images, "colors": COLORS,
  33. "evaluations": evs
  34. })
  35. # ── 科目管理 ────────────────────────────────────────────
  36. @app.get("/subjects", response_class=HTMLResponse)
  37. async def subjects_page(request: Request, session: Session = Depends(get_session)):
  38. subjects = crud.get_all_subjects(session)
  39. return templates.TemplateResponse("subjects.html", {"request": request, "subjects": subjects, "colors": COLORS})
  40. @app.post("/subjects/create")
  41. async def create_subject(
  42. request: Request,
  43. name: str = Form(...),
  44. color: str = Form(default="#6366f1"),
  45. session: Session = Depends(get_session)
  46. ):
  47. subject = crud.create_subject(session, name.strip(), color)
  48. return RedirectResponse(url=f"/subject/{subject.id}", status_code=303)
  49. @app.post("/subjects/{subject_id}/delete")
  50. async def delete_subject(subject_id: int, session: Session = Depends(get_session)):
  51. if crud.delete_subject(session, subject_id):
  52. return RedirectResponse(url="/subjects", status_code=303)
  53. raise HTTPException(404, "科目不存在")
  54. # ── 科目详情页 ─────────────────────────────────────────
  55. @app.get("/subject/{subject_id}", response_class=HTMLResponse)
  56. async def subject_detail(
  57. request: Request, subject_id: int, date: str | None = None,
  58. session: Session = Depends(get_session)
  59. ):
  60. subject = session.get(Subject, subject_id)
  61. if not subject:
  62. raise HTTPException(404, "科目不存在")
  63. dates = crud.get_dates_for_subject(session, subject_id)
  64. if not date:
  65. date = dates[0] if dates else datetime.now().strftime("%Y-%m-%d")
  66. images = crud.get_subject_images(session, subject_id, date)
  67. if not images and dates:
  68. date = dates[0]
  69. images = crud.get_subject_images(session, subject_id, date)
  70. prev_date = None
  71. next_date = None
  72. if dates and date in dates:
  73. idx = dates.index(date)
  74. if idx < len(dates) - 1:
  75. prev_date = dates[idx + 1]
  76. if idx > 0:
  77. next_date = dates[idx - 1]
  78. return templates.TemplateResponse("subject_detail.html", {
  79. "request": request, "subject": subject, "dates": dates,
  80. "current_date": date, "images": images,
  81. "prev_date": prev_date, "next_date": next_date,
  82. "subjects": crud.get_all_subjects(session)
  83. })
  84. # ── 上传错题图片 ────────────────────────────────────────
  85. @app.post("/subject/{subject_id}/upload")
  86. async def upload_error_images(
  87. subject_id: int,
  88. date: str = Form(...),
  89. files: list[UploadFile] = File(...),
  90. session: Session = Depends(get_session)
  91. ):
  92. subject = session.get(Subject, subject_id)
  93. if not subject:
  94. raise HTTPException(404, "科目不存在")
  95. filenames, original_names = [], []
  96. for f in files:
  97. content = await f.read()
  98. ext = Path(f.filename).suffix or ".jpg"
  99. unique_name = f"{uuid.uuid4().hex}{ext}"
  100. dest = UPLOAD_DIR / unique_name
  101. dest.write_bytes(content)
  102. filenames.append(unique_name)
  103. original_names.append(f.filename)
  104. crud.add_error_images(session, subject_id, date, filenames, original_names)
  105. return RedirectResponse(url=f"/subject/{subject_id}?date={date}", status_code=303)
  106. # ── 删除单张图片 ────────────────────────────────────────
  107. @app.post("/image/{image_id}/delete")
  108. async def delete_image(image_id: int, session: Session = Depends(get_session)):
  109. img = crud.delete_image(session, image_id)
  110. if not img:
  111. raise HTTPException(404, "图片不存在")
  112. return RedirectResponse(url=f"/subject/{img.subject_id}?date={img.date}", status_code=303)
  113. # ── 时间线 ───────────────────────────────────────────────
  114. @app.get("/timeline", response_class=HTMLResponse)
  115. async def timeline(request: Request, date: str | None = None, session: Session = Depends(get_session)):
  116. subjects = crud.get_all_subjects(session)
  117. all_dates = crud.get_all_dates(session)
  118. if not date:
  119. date = all_dates[0] if all_dates else datetime.now().strftime("%Y-%m-%d")
  120. timeline_data = []
  121. for s in subjects:
  122. imgs = crud.get_subject_images(session, s.id, date)
  123. if imgs:
  124. timeline_data.append({"subject": s, "images": imgs})
  125. prev_date = None
  126. next_date = None
  127. if all_dates and date in all_dates:
  128. idx = all_dates.index(date)
  129. if idx < len(all_dates) - 1:
  130. prev_date = all_dates[idx + 1]
  131. if idx > 0:
  132. next_date = all_dates[idx - 1]
  133. return templates.TemplateResponse("timeline.html", {
  134. "request": request, "subjects": subjects, "all_dates": all_dates,
  135. "current_date": date, "timeline_data": timeline_data,
  136. "prev_date": prev_date, "next_date": next_date
  137. })
  138. # ── 复习汇总 ────────────────────────────────────────────
  139. @app.get("/review", response_class=HTMLResponse)
  140. async def review_page(request: Request, session: Session = Depends(get_session)):
  141. data = crud.get_review_data(session)
  142. subjects = crud.get_all_subjects(session)
  143. return templates.TemplateResponse("review.html", {
  144. "request": request, "review_data": data, "subjects": subjects
  145. })
  146. # ── 知识评价 ────────────────────────────────────────────
  147. @app.get("/evaluations", response_class=HTMLResponse)
  148. async def evaluations_page(request: Request, subject_id: int | None = None, session: Session = Depends(get_session)):
  149. subjects = crud.get_all_subjects(session)
  150. evs = crud.get_evaluations(session, subject_id)
  151. return templates.TemplateResponse("evaluations.html", {
  152. "request": request, "subjects": subjects, "evaluations": evs,
  153. "current_subject": subject_id, "today": datetime.now().strftime("%Y-%m-%d")
  154. })
  155. @app.post("/evaluations/add")
  156. async def add_evaluation(
  157. subject_id: int = Form(...),
  158. date: str = Form(...),
  159. title: str = Form(...),
  160. content: str = Form(default=""),
  161. file: UploadFile | None = File(None),
  162. session: Session = Depends(get_session)
  163. ):
  164. filename = None
  165. if file and file.filename:
  166. ext = Path(file.filename).suffix or ".pdf"
  167. unique_name = f"eval_{uuid.uuid4().hex}{ext}"
  168. dest = UPLOAD_DIR / unique_name
  169. dest.write_bytes(await file.read())
  170. filename = unique_name
  171. crud.add_evaluation(session, subject_id, date, title, content, filename)
  172. return RedirectResponse(url=f"/evaluations?subject_id={subject_id}", status_code=303)
  173. @app.post("/evaluation/{eval_id}/delete")
  174. async def delete_evaluation(eval_id: int, session: Session = Depends(get_session)):
  175. ev = session.get(Evaluation, eval_id)
  176. if not ev:
  177. raise HTTPException(404)
  178. if ev.filename:
  179. p = UPLOAD_DIR / ev.filename
  180. if p.exists():
  181. p.unlink()
  182. session.delete(ev)
  183. session.commit()
  184. return RedirectResponse(url=f"/evaluations?subject_id={ev.subject_id}", status_code=303)
  185. if __name__ == "__main__":
  186. import uvicorn
  187. uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)