diff --git a/app/routers/web.py b/app/routers/web.py index d5ab954..c464d4f 100644 --- a/app/routers/web.py +++ b/app/routers/web.py @@ -456,26 +456,80 @@ async def upload_image( return _redirect(f"/postcards/{postcard_id}") -# ---------- API Keys ---------- +# ---------- Showcase ---------- -@router.get("/api-keys", response_class=HTMLResponse) -def api_keys_page(request: Request, user: User = Depends(get_current_user_web), db: Session = Depends(get_db)): - keys = db.query(ApiKey).filter(ApiKey.user_id == user.id).all() - return templates.TemplateResponse("api_keys.html", {"request": request, "user": user, "keys": keys}) +@router.get("/showcase/manage", response_class=HTMLResponse) +def showcase_manage(request: Request, user: User = Depends(get_current_user_web), db: Session = Depends(get_db)): + profiles = db.query(Profile).filter(Profile.user_id == user.id).all() + profile_cards = {} + for p in profiles: + cards = db.query(Postcard).filter(Postcard.profile_id == p.id).all() + profile_cards[p.id] = cards + return templates.TemplateResponse("showcase_manage.html", { + "request": request, "user": user, "profiles": profiles, "profile_cards": profile_cards, + }) -@router.post("/api-keys") -def api_key_create(user: User = Depends(get_current_user_web), db: Session = Depends(get_db)): - ak = ApiKey(user_id=user.id, key=ApiKey.generate_key()) - db.add(ak) - db.commit() - return _redirect("/api-keys") - - -@router.post("/api-keys/{key_id}/delete") -def api_key_delete(key_id: int, user: User = Depends(get_current_user_web), db: Session = Depends(get_db)): - ak = db.query(ApiKey).filter(ApiKey.id == key_id, ApiKey.user_id == user.id).first() - if ak: - db.delete(ak) +@router.post("/showcase/manage") +def showcase_manage_save(request: Request, showcase_mode: str = Form("profile"), user: User = Depends(get_current_user_web), db: Session = Depends(get_db)): + if showcase_mode in ("profile", "flat"): + user.showcase_mode = showcase_mode db.commit() - return _redirect("/api-keys") + return _redirect("/showcase/manage") + + +@router.post("/profiles/{profile_id}/showcase") +def profile_showcase_save( + profile_id: int, + showcase_enabled: str = Form(""), + showcase_visible: str = Form("both"), + user: User = Depends(get_current_user_web), + db: Session = Depends(get_db), +): + p = db.query(Profile).filter(Profile.id == profile_id, Profile.user_id == user.id).first() + if p: + p.showcase_enabled = showcase_enabled == "1" + if showcase_visible in ("sent", "received", "both"): + p.showcase_visible = showcase_visible + db.commit() + return _redirect("/showcase/manage") + + +@router.post("/postcards/{postcard_id}/showcase-toggle") +def postcard_showcase_toggle(postcard_id: int, showcase_hidden: str = Form(""), user: User = Depends(get_current_user_web), db: Session = Depends(get_db)): + pc = db.query(Postcard).filter(Postcard.id == postcard_id).first() + if pc: + profile = db.query(Profile).filter(Profile.id == pc.profile_id, Profile.user_id == user.id).first() + if profile and profile.showcase_enabled: + pc.showcase_hidden = showcase_hidden == "1" + db.commit() + return _redirect("/showcase/manage") + + +@router.get("/u/{username}", response_class=HTMLResponse) +def public_showcase(request: Request, username: str, db: Session = Depends(get_db)): + user = db.query(User).filter(User.username == username).first() + if not user: + return HTMLResponse("用户不存在", status_code=404) + profiles = db.query(Profile).filter(Profile.user_id == user.id, Profile.showcase_enabled == True).all() + sent_all, received_all, profile_groups = [], [], [] + for p in profiles: + cards = db.query(Postcard).filter(Postcard.profile_id == p.id, Postcard.showcase_hidden == False).all() + if p.showcase_visible in ("sent", "both"): + sent_all.extend([c for c in cards if c.status in ("sent", "delivered")]) + if p.showcase_visible in ("received", "both"): + received_all.extend([c for c in cards if c.status == "received"]) + if p.showcase_visible in ("sent", "both"): + p_sent = [c for c in cards if c.status in ("sent", "delivered")] + else: + p_sent = [] + if p.showcase_visible in ("received", "both"): + p_received = [c for c in cards if c.status == "received"] + else: + p_received = [] + if p_sent or p_received: + profile_groups.append({"profile": p, "sent": p_sent, "received": p_received}) + return templates.TemplateResponse("showcase.html", { + "request": request, "profile_user": user, "mode": user.showcase_mode, + "profile_groups": profile_groups, "sent_all": sent_all, "received_all": received_all, + }) diff --git a/app/static/style.css b/app/static/style.css index 82a0180..9b7f155 100644 --- a/app/static/style.css +++ b/app/static/style.css @@ -226,6 +226,27 @@ code { background: var(--bg); padding: .15rem .4rem; border-radius: 4px; font-si /* Empty state */ .empty { color: var(--text-muted); padding: 2rem; text-align: center; } +/* Toggle switch */ +.toggle-switch { position: relative; display: inline-block; width: 36px; height: 20px; } +.toggle-switch input { opacity: 0; width: 0; height: 0; } +.toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background: var(--border); border-radius: 20px; transition: .2s; } +.toggle-slider::before { content: ""; position: absolute; height: 16px; width: 16px; left: 2px; bottom: 2px; background: #fff; border-radius: 50%; transition: .2s; } +.toggle-switch input:checked + .toggle-slider { background: var(--primary); } +.toggle-switch input:checked + .toggle-slider::before { transform: translateX(16px); } +.toggle-sm { width: 30px; height: 18px; } +.toggle-sm .toggle-slider::before { height: 14px; width: 14px; } +.toggle-sm input:checked + .toggle-slider::before { transform: translateX(12px); } + +/* Showcase */ +.showcase-card { display: block; background: var(--card-bg); border: 1px solid var(--border); border-radius: 10px; overflow: hidden; transition: box-shadow .15s; text-decoration: none; color: var(--text); } +.showcase-card:hover { box-shadow: 0 4px 16px rgba(0,0,0,.08); text-decoration: none; } +.showcase-card-img { width: 100%; aspect-ratio: 3/2; object-fit: cover; display: block; background: var(--bg); } +.showcase-card-img.placeholder-img { display: flex; align-items: center; justify-content: center; font-size: 2rem; } +.showcase-card-body { padding: .75rem; } +.showcase-card-body strong { font-size: .9rem; } +.showcase-card-meta { display: flex; gap: .5rem; font-size: .8rem; color: var(--text-muted); margin-top: .25rem; } +.showcase-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: .75rem; margin-bottom: 1rem; } + /* Responsive */ @media (max-width: 640px) { .detail-grid { grid-template-columns: 1fr; } @@ -247,4 +268,8 @@ code { background: var(--bg); padding: .15rem .4rem; border-radius: 4px; font-si .btn-back { padding: .25rem .5rem; font-size: .85rem; } .profile-grid { grid-template-columns: 1fr; } .profile-card-stats { gap: .75rem; flex-wrap: wrap; } + .showcase-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: .5rem; } + .showcase-card-meta { flex-wrap: wrap; } + .detail-title-row h1 { font-size: 1.2rem; } + .btn-back { padding: .25rem .5rem; font-size: .85rem; } } diff --git a/app/templates/profiles.html b/app/templates/profiles.html index 36201ab..4143b45 100644 --- a/app/templates/profiles.html +++ b/app/templates/profiles.html @@ -52,6 +52,7 @@ 创建于 {{ p.created_at.strftime('%Y-%m-%d') }}
暂无展示内容
++ 公开展示页地址:/u/{{ user.username }} +
+单张明信片展示控制(关闭展示后可单独隐藏):
+ {% for pc in pc_list %} + + {% endfor %} +暂无 Profile,请先创建
+