From de29f5445e23a639e207841b35ffd39ef8fa44be Mon Sep 17 00:00:00 2001 From: Zichao Lin Date: Wed, 15 Jul 2026 19:04:54 +0800 Subject: [PATCH] feat(postcard): split create into sent/received, add pending status and mark-sent flow --- app/locales/en.json | 9 ++ app/locales/zh.json | 9 ++ app/routers/web.py | 41 +++++-- app/static/style.css | 1 + app/templates/postcard_detail.html | 14 ++- app/templates/postcard_form.html | 169 ++++++++++++++++------------- app/templates/postcards.html | 20 +++- 7 files changed, 172 insertions(+), 91 deletions(-) diff --git a/app/locales/en.json b/app/locales/en.json index 5ef955e..e696947 100644 --- a/app/locales/en.json +++ b/app/locales/en.json @@ -73,17 +73,23 @@ "profiles.confirm_delete": "Delete this profile?", "pc_list.title": "'s postcards", "pc_list.new": "+ New postcard", + "pc_list.new_sent": "✉️ Sent", + "pc_list.new_received": "📬 Received", "pc_list.duplicate": "Card number already exists", "pc_list.all_countries": "All countries", "pc_list.filter": "Filter", "pc_list.tab_sent": "Sent", "pc_list.tab_received": "Received", + "pc_list.mark_sent": "Send", + "pc_list.confirm_sent": "Mark as sent?", "pc_list.mark_delivered": "Delivered", "pc_list.confirm_delivered": "Mark as delivered?", "pc_list.empty_sent": "No sent postcards.", "pc_list.empty_received": "No received postcards.", "pc_detail.title": "Postcard detail", "pc_detail.edit": "Edit", + "pc_detail.mark_sent": "Send", + "pc_detail.confirm_sent": "Mark as sent?", "pc_detail.mark_delivered": "Mark delivered", "pc_detail.confirm_delivered": "Mark as delivered?", "pc_detail.delete": "Delete", @@ -106,10 +112,13 @@ "pc_detail.profile": "Profile", "pc_detail.notes": "Notes", "status.sent": "Sent", + "status.pending": "Pending", "status.delivered": "Delivered", "status.received": "Received", "pc_form.edit": "Edit postcard", "pc_form.new": "New postcard", + "pc_form.new_sent": "New sent", + "pc_form.new_received": "New received", "pc_form.number": "Number", "pc_form.number_hint": "Format: AB-1234567", "pc_form.status": "Status", diff --git a/app/locales/zh.json b/app/locales/zh.json index d5ac648..3163d48 100644 --- a/app/locales/zh.json +++ b/app/locales/zh.json @@ -73,17 +73,23 @@ "profiles.confirm_delete": "确定删除此 Profile?", "pc_list.title": " 的明信片", "pc_list.new": "+ 新明信片", + "pc_list.new_sent": "✉️ 寄出", + "pc_list.new_received": "📬 收到", "pc_list.duplicate": "编号已存在", "pc_list.all_countries": "全部国家", "pc_list.filter": "筛选", "pc_list.tab_sent": "寄出", "pc_list.tab_received": "收到", + "pc_list.mark_sent": "寄出", + "pc_list.confirm_sent": "确认寄出?", "pc_list.mark_delivered": "已送达", "pc_list.confirm_delivered": "确认已送达?", "pc_list.empty_sent": "没有寄出的明信片。", "pc_list.empty_received": "没有收到的明信片。", "pc_detail.title": "明信片详情", "pc_detail.edit": "编辑", + "pc_detail.mark_sent": "寄出", + "pc_detail.confirm_sent": "确认寄出?", "pc_detail.mark_delivered": "已送达", "pc_detail.confirm_delivered": "确认已送达?", "pc_detail.delete": "删除", @@ -106,10 +112,13 @@ "pc_detail.profile": "所属 Profile", "pc_detail.notes": "备注", "status.sent": "已寄出", + "status.pending": "待寄出", "status.delivered": "已送达", "status.received": "已收到", "pc_form.edit": "编辑明信片", "pc_form.new": "新建明信片", + "pc_form.new_sent": "新建寄出", + "pc_form.new_received": "新建收到", "pc_form.number": "编号", "pc_form.number_hint": "格式:AB-1234567", "pc_form.status": "状态", diff --git a/app/routers/web.py b/app/routers/web.py index af536b5..5c9a62d 100644 --- a/app/routers/web.py +++ b/app/routers/web.py @@ -131,7 +131,7 @@ def index_page(request: Request, db: Session = Depends(get_db)): ).all() for c in cards: visible = True - if c.status in ("sent", "delivered") and p.showcase_visible not in ("sent", "both"): + if c.status in ("pending", "sent", "delivered") and p.showcase_visible not in ("sent", "both"): visible = False if c.status == "received" and p.showcase_visible not in ("received", "both"): visible = False @@ -173,7 +173,7 @@ def api_public_cards( ).all() for c in cards: vis = True - if c.status in ("sent", "delivered") and p.showcase_visible not in ("sent", "both"): + if c.status in ("pending", "sent", "delivered") and p.showcase_visible not in ("sent", "both"): vis = False if c.status == "received" and p.showcase_visible not in ("received", "both"): vis = False @@ -218,7 +218,7 @@ def api_public_cards_user( Postcard.image_front.isnot(None), Postcard.image_front != "" ).all() if tab == "sent" and p.showcase_visible in ("sent", "both"): - all_cards.extend([c for c in cards if c.status in ("sent", "delivered")]) + all_cards.extend([c for c in cards if c.status in ("pending", "sent", "delivered")]) elif tab == "received" and p.showcase_visible in ("received", "both"): all_cards.extend([c for c in cards if c.status == "received"]) all_cards.sort(key=lambda c: c.send_time or c.created_at, reverse=True) @@ -613,7 +613,7 @@ def dashboard(request: Request, user: User = Depends(get_current_user_web), db: for c in all_cards: if not c.country_to: continue - if c.status in ("sent", "delivered"): + if c.status in ("pending", "sent", "delivered"): sent_country_counts[c.country_to] = sent_country_counts.get(c.country_to, 0) + 1 elif c.status == "received": received_country_counts[c.country_to] = received_country_counts.get(c.country_to, 0) + 1 @@ -712,7 +712,7 @@ def postcard_list( postcards.sort(key=lambda pc: (_status_order.get(pc.status, 9), -(pc.send_time or pc.arrival_time or pc.receive_time or pc.created_at).timestamp())) # split into sent and received - sent = [pc for pc in postcards if pc.status in ("sent", "delivered")] + sent = [pc for pc in postcards if pc.status in ("pending", "sent", "delivered")] received = [pc for pc in postcards if pc.status == "received"] return templates.TemplateResponse( @@ -722,11 +722,12 @@ def postcard_list( @router.get("/profiles/{profile_id}/postcards/new", response_class=HTMLResponse) -def postcard_new(request: Request, profile_id: int, user: User = Depends(get_current_user_web), db: Session = Depends(get_db)): +def postcard_new(request: Request, profile_id: int, create_type: str = Query("sent"), user: User = Depends(get_current_user_web), db: Session = Depends(get_db)): profile = db.query(Profile).filter(Profile.id == profile_id, Profile.user_id == user.id).first() if not profile: return _redirect("/profiles") - return templates.TemplateResponse("postcard_form.html", {"request": request, "user": user, "profile": profile, "postcard": None}) + today = datetime.now().strftime("%Y-%m-%d") + return templates.TemplateResponse("postcard_form.html", {"request": request, "user": user, "profile": profile, "postcard": None, "create_type": create_type, "today": today}) @router.post("/profiles/{profile_id}/postcards") @@ -916,6 +917,28 @@ def mark_delivered( return _redirect(f"/postcards/{postcard_id}") +@router.post("/postcards/{postcard_id}/mark-sent") +def mark_sent( + postcard_id: int, + user: User = Depends(get_current_user_web), + db: Session = Depends(get_db), +): + """Transition pending → sent, auto-fill send_time if empty.""" + pc = ( + db.query(Postcard) + .join(Profile) + .filter(Postcard.id == postcard_id, Profile.user_id == user.id) + .first() + ) + if not pc: + return _redirect("/profiles") + pc.status = "sent" + if not pc.send_time: + pc.send_time = datetime.now() + db.commit() + return _redirect(f"/postcards/{postcard_id}") + + # ---------- Image Upload ---------- @router.post("/postcards/{postcard_id}/image/{side}") @@ -1028,11 +1051,11 @@ def public_showcase(request: Request, username: str, db: Session = Depends(get_d Postcard.image_front.isnot(None), Postcard.image_front != "" ).all() if p.showcase_visible in ("sent", "both"): - sent_all.extend([c for c in cards if c.status in ("sent", "delivered")]) + sent_all.extend([c for c in cards if c.status in ("pending", "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")] + p_sent = [c for c in cards if c.status in ("pending", "sent", "delivered")] else: p_sent = [] if p.showcase_visible in ("received", "both"): diff --git a/app/static/style.css b/app/static/style.css index 92cf5b4..ce058f3 100644 --- a/app/static/style.css +++ b/app/static/style.css @@ -156,6 +156,7 @@ code { background: var(--bg); padding: .15rem .4rem; border-radius: 4px; font-si /* Badge */ .badge { display: inline-block; padding: .1rem .4rem; border-radius: 999px; font-size: .65rem; font-weight: 600; color: #fff; background: var(--text-muted); white-space: nowrap; line-height: 1.4; align-self: center; } .badge-sent { background: var(--sent); } +.badge-pending { background: var(--primary); } .badge-delivered { background: var(--delivered); } .badge-received { background: var(--received); } diff --git a/app/templates/postcard_detail.html b/app/templates/postcard_detail.html index fedc0ac..f040361 100644 --- a/app/templates/postcard_detail.html +++ b/app/templates/postcard_detail.html @@ -5,11 +5,15 @@

{{ postcard.card_number }}

- {{ {'sent':'已寄出','delivered':'已送达','received':'已收到'}.get(postcard.status, postcard.status) if user.language=='zh' else {'sent':'Sent','delivered':'Delivered','received':'Received'}.get(postcard.status, postcard.status) }} + {{ {'pending':'待寄出','sent':'已寄出','delivered':'已送达','received':'已收到'}.get(postcard.status, postcard.status) if user.language=='zh' else {'pending':'Pending','sent':'Sent','delivered':'Delivered','received':'Received'}.get(postcard.status, postcard.status) }}
{{ 'pc_detail.edit'|t(user.language) }} - {% if postcard.status == 'sent' %} + {% if postcard.status == 'pending' %} +
+ +
+ {% elif postcard.status == 'sent' %}
@@ -49,12 +53,14 @@
{{ 'pc_detail.card_number'|t(user.language) }}
{{ postcard.card_number }}
-
{{ 'pc_detail.status'|t(user.language) }}
{{ {'sent':'已寄出','delivered':'已送达','received':'已收到'}.get(postcard.status, postcard.status) if user.language=='zh' else {'sent':'Sent','delivered':'Delivered','received':'Received'}.get(postcard.status, postcard.status) }}
- {% if postcard.status in ('sent','delivered') %} +
{{ 'pc_detail.status'|t(user.language) }}
{{ {'pending':'待寄出','sent':'已寄出','delivered':'已送达','received':'已收到'}.get(postcard.status, postcard.status) if user.language=='zh' else {'pending':'Pending','sent':'Sent','delivered':'Delivered','received':'Received'}.get(postcard.status, postcard.status) }}
+ {% if postcard.status in ('pending','sent','delivered') %}
{{ 'pc_detail.recipient'|t(user.language) }}
{{ postcard.recipient_name or '-' }}
{{ 'pc_detail.route'|t(user.language) }}
{% if postcard.country_from %}{{ postcard.country_from|flag }} {{ postcard.country_from }} → {% endif %}{{ postcard.country_to|flag }} {{ postcard.country_to or '-' }}
{{ 'pc_detail.send_time'|t(user.language) }}
{{ postcard.send_time.strftime('%Y-%m-%d') if postcard.send_time else '-' }}
+ {% if postcard.status == 'delivered' %}
{{ 'pc_detail.arrival_time'|t(user.language) }}
{{ postcard.arrival_time.strftime('%Y-%m-%d') if postcard.arrival_time else '-' }}
+ {% endif %} {% else %}
{{ 'pc_detail.sender'|t(user.language) }}
{{ postcard.sender_name or '-' }}
{{ 'pc_detail.route'|t(user.language) }}
{% if postcard.country_from %}{{ postcard.country_from|flag }} {{ postcard.country_from }} → {% endif %}{{ postcard.country_to|flag }} {{ postcard.country_to or '-' }}
diff --git a/app/templates/postcard_form.html b/app/templates/postcard_form.html index 4396445..b6075ff 100644 --- a/app/templates/postcard_form.html +++ b/app/templates/postcard_form.html @@ -1,44 +1,73 @@ {% extends "base.html" %} -{% block title %}{{ ('pc_form.edit'|t(user.language)) if postcard else ('pc_form.new'|t(user.language)) }} - {{ 'brand'|t(user.language) }}{% endblock %} +{% block title %}{% if postcard %}{{ 'pc_form.edit'|t(user.language) }}{% else %}{% if create_type == 'received' %}{{ 'pc_form.new_received'|t(user.language) }}{% else %}{{ 'pc_form.new_sent'|t(user.language) }}{% endif %}{% endif %} - {{ 'brand'|t(user.language) }}{% endblock %} {% block content %}
-

{{ ('pc_form.edit'|t(user.language)) if postcard else ('pc_form.new'|t(user.language)) }}

+

{% if postcard %}{{ 'pc_form.edit'|t(user.language) }}{% else %}{% if create_type == 'received' %}{{ 'pc_form.new_received'|t(user.language) }}{% else %}{{ 'pc_form.new_sent'|t(user.language) }}{% endif %}{% endif %}

-
- - +{% if error %}
{{ error }}
{% endif %} + +{% set is_new = (postcard is none) %} +{% set is_edit = (postcard is not none) %} +{% set edit_status = postcard.status if is_edit else '' %} +{% set is_received_form = (create_type == 'received' and is_new) or (is_edit and edit_status == 'received') %} + + + + + + {% if is_received_form %} + {# ── 收到表单:无状态选择 ── #} + + + + + + + + + + + + + + + {% else %} + {# ── 寄出表单 ── #} + {% if is_new %} + {# 新建寄出:状态由日期自动决定,隐藏选择 #} + + +
+ {% else %} + {# 编辑时:显示状态选择 #} + {% endif %}
- + - + - -
- - - + - - + + + + {% endif %} - +
@@ -46,68 +75,62 @@
diff --git a/app/templates/postcards.html b/app/templates/postcards.html index ee0ce02..4ff94ce 100644 --- a/app/templates/postcards.html +++ b/app/templates/postcards.html @@ -3,7 +3,10 @@ {% block content %}
{% if profile.notes %} @@ -41,10 +44,10 @@
{{ pc.card_number }}
- {% if pc.country_from %}{{ pc.country_from|flag }}→{% endif %}{{ pc.country_to|flag }} {{ pc.country_to or '-' }} + {% if pc.country_from %}{{ pc.country_from|flag }} {{ pc.country_from }} → {% endif %}{{ pc.country_to|flag }} {{ pc.country_to or '-' }} → {{ pc.recipient_name or '-' }}
- {{ {'sent':'已寄出','delivered':'已送达'}.get(pc.status) if user.language=='zh' else {'sent':'Sent','delivered':'Delivered'}.get(pc.status) }} + {{ {'pending':'待寄出','sent':'已寄出','delivered':'已送达'}.get(pc.status) if user.language=='zh' else {'pending':'Pending','sent':'Sent','delivered':'Delivered'}.get(pc.status) }} {% if pc.send_time %}{{ pc.send_time.strftime('%Y-%m-%d') }}{% if user.language=='zh' %}寄出{% endif %}{% endif %} {% if pc.arrival_time %}
{{ pc.arrival_time.strftime('%Y-%m-%d') }}{% if user.language=='zh' %}送达{% endif %}{% endif %} @@ -52,7 +55,11 @@
{% if pc.notes %}{{ pc.notes[:50] }}{% if pc.notes|length > 50 %}…{% endif %}{% endif %}
- {% if pc.status == 'sent' %} + {% if pc.status == 'pending' %} +
+ +
+ {% elif pc.status == 'sent' %}
@@ -81,7 +88,10 @@
← {{ pc.sender_name or '-' }}
- {% if pc.receive_time %}{{ pc.receive_time.strftime('%Y-%m-%d') }}{% if user.language=='zh' %}收到{% endif %}{% endif %} + {{ 'status.received'|t(user.language) }} + + {% if pc.receive_time %}{{ pc.receive_time.strftime('%Y-%m-%d') }}{% if user.language=='zh' %}收到{% endif %}{% endif %} +
{% if pc.notes %}{{ pc.notes[:50] }}{% if pc.notes|length > 50 %}…{% endif %}{% endif %}