From 314fc16b98ea2ba9e88afaedb5d83467708cae76 Mon Sep 17 00:00:00 2001 From: Tianyu Liu Date: Sun, 19 Apr 2026 13:31:17 +0200 Subject: [PATCH] ux refine --- app/main.py | 20 ++++++- app/static/style.css | 12 ++++ app/templates/items/form.html | 9 ++- app/templates/subitems/form.html | 9 ++- tests/test_app.py | 99 ++++++++++++++++++++++++++++++-- 5 files changed, 139 insertions(+), 10 deletions(-) diff --git a/app/main.py b/app/main.py index ce6cd0f..71dbca8 100644 --- a/app/main.py +++ b/app/main.py @@ -82,6 +82,10 @@ def _image_response_or_404(target) -> Response: return Response(content=target.image_blob, media_type=target.image_mime_type) +def _wants_add_next(submit_action: str | None) -> bool: + return submit_action == "save_and_add_next" + + def _build_search_results(db: Session, query: str) -> list[dict]: keyword = f"%{query.lower()}%" results: list[dict] = [] @@ -333,6 +337,7 @@ def create_app() -> FastAPI: note: str | None = Form(default=None), quantity: str | None = Form(default=None), is_container: str | None = Form(default=None), + submit_action: str | None = Form(default=None), image_file: UploadFile | None = File(default=None), db: Session = Depends(get_db), ) -> RedirectResponse: @@ -348,7 +353,13 @@ def create_app() -> FastAPI: db.add(item) db.commit() db.refresh(item) - return RedirectResponse(url=f"/items/{item.id}", status_code=status.HTTP_303_SEE_OTHER) + if _wants_add_next(submit_action): + redirect_url = f"/boxes/{box.id}/items/new" + elif item.is_container: + redirect_url = f"/items/{item.id}" + else: + redirect_url = f"/boxes/{box.id}" + return RedirectResponse(url=redirect_url, status_code=status.HTTP_303_SEE_OTHER) @app.get("/items/{item_id}") def show_item(item_id: int, request: Request, db: Session = Depends(get_db)): @@ -436,6 +447,7 @@ def create_app() -> FastAPI: name: str = Form(...), note: str | None = Form(default=None), quantity: str | None = Form(default=None), + submit_action: str | None = Form(default=None), image_file: UploadFile | None = File(default=None), db: Session = Depends(get_db), ) -> RedirectResponse: @@ -451,7 +463,11 @@ def create_app() -> FastAPI: db.add(subitem) db.commit() db.refresh(subitem) - return RedirectResponse(url=f"/items/{item.id}", status_code=status.HTTP_303_SEE_OTHER) + if _wants_add_next(submit_action): + redirect_url = f"/items/{item.id}/subitems/new" + else: + redirect_url = f"/items/{item.id}" + return RedirectResponse(url=redirect_url, status_code=status.HTTP_303_SEE_OTHER) @app.get("/subitems/{subitem_id}/image") def get_subitem_image(subitem_id: int, db: Session = Depends(get_db)) -> Response: diff --git a/app/static/style.css b/app/static/style.css index f25d25c..5fca068 100644 --- a/app/static/style.css +++ b/app/static/style.css @@ -63,6 +63,12 @@ button, padding: 10px 14px; } +.button-secondary { + background: #eef3f8; + color: #1f2937; + border: 1px solid #cbd5e1; +} + .button:hover, button:hover { opacity: 0.92; @@ -280,6 +286,12 @@ button:hover { gap: 14px; } +.form-actions { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + .form-field { margin: 0; } diff --git a/app/templates/items/form.html b/app/templates/items/form.html index 33a686e..d25350e 100644 --- a/app/templates/items/form.html +++ b/app/templates/items/form.html @@ -77,6 +77,13 @@ {% endif %} - +
+ + {% if not item %} + + {% endif %} +
{% endblock %} diff --git a/app/templates/subitems/form.html b/app/templates/subitems/form.html index 497e071..357af76 100644 --- a/app/templates/subitems/form.html +++ b/app/templates/subitems/form.html @@ -72,6 +72,13 @@ {% endif %} - +
+ + {% if not subitem %} + + {% endif %} +
{% endblock %} diff --git a/tests/test_app.py b/tests/test_app.py index 4073e8d..3dfdc6d 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -21,19 +21,28 @@ def create_item( quantity="2", is_container=False, image=None, + submit_action="save", ): - data = {"name": name, "note": note, "quantity": quantity} + data = {"name": name, "note": note, "quantity": quantity, "submit_action": submit_action} if is_container: data["is_container"] = "on" files = {"image_file": image} if image is not None else None return client.post(f"/boxes/{box_id}/items", data=data, files=files, follow_redirects=False) -def create_subitem(client, item_id, name="SubItem A", note="Small", quantity="3", image=None): +def create_subitem( + client, + item_id, + name="SubItem A", + note="Small", + quantity="3", + image=None, + submit_action="save", +): files = {"image_file": image} if image is not None else None return client.post( f"/items/{item_id}/subitems", - data={"name": name, "note": note, "quantity": quantity}, + data={"name": name, "note": note, "quantity": quantity, "submit_action": submit_action}, files=files, follow_redirects=False, ) @@ -320,14 +329,13 @@ def test_post_redirects_are_reasonable(client, db_session): db_session.commit() item_response = create_item(client, box.id, name="Lamp") - item_id = int(item_response.headers["location"].split("/")[-1]) - item = db_session.get(Item, item_id) + item = db_session.query(Item).one() item.is_container = True db_session.commit() subitem_response = create_subitem(client, item.id, name="Bulb") - assert item_response.headers["location"] == f"/items/{item.id}" + assert item_response.headers["location"] == f"/boxes/{box.id}" assert subitem_response.headers["location"] == f"/items/{item.id}" @@ -730,6 +738,7 @@ def test_new_item_page_shows_clear_context_and_default_quantity(client, db_sessi assert 'name="quantity"' in response.text assert 'value="1"' in response.text assert "这个物品本身是一个小容器" in response.text + assert "保存并添加下一个" in response.text def test_new_subitem_page_shows_clear_context_and_default_quantity(client, db_session): @@ -746,6 +755,7 @@ def test_new_subitem_page_shows_clear_context_and_default_quantity(client, db_se assert "文件袋" in response.text assert 'name="quantity"' in response.text assert 'value="1"' in response.text + assert "保存并添加下一个" in response.text def test_box_detail_page_renders_clear_hierarchy_and_dense_list_structure(client, db_session): @@ -775,3 +785,80 @@ def test_item_detail_page_renders_clear_hierarchy(client, db_session): assert "容器型 Item" in response.text assert "书房箱" in response.text assert "SubItem" in response.text + + +def test_creating_regular_item_redirects_back_to_parent_box(client, db_session): + box = Box(name="连续录入箱") + db_session.add(box) + db_session.commit() + + response = create_item(client, box.id, name="剪刀", is_container=False) + + assert response.status_code == 303 + assert response.headers["location"] == f"/boxes/{box.id}" + + +def test_creating_regular_subitem_redirects_back_to_parent_item(client, db_session): + box = Box(name="配件箱") + item = Item(name="线材袋", box=box, is_container=True) + db_session.add_all([box, item]) + db_session.commit() + + response = create_subitem(client, item.id, name="USB头") + + assert response.status_code == 303 + assert response.headers["location"] == f"/items/{item.id}" + + +def test_creating_box_redirects_to_new_box_detail(client): + response = create_box(client, name="新箱子") + + assert response.status_code == 303 + assert response.headers["location"].startswith("/boxes/") + assert not response.headers["location"].endswith("/items/new") + + +def test_creating_container_item_redirects_to_item_detail(client, db_session): + box = Box(name="子容器箱") + db_session.add(box) + db_session.commit() + + response = create_item(client, box.id, name="小收纳盒", is_container=True) + + created_item = db_session.query(Item).one() + assert response.status_code == 303 + assert response.headers["location"] == f"/items/{created_item.id}" + + +def test_creating_item_with_save_and_add_next_returns_to_same_new_item_context(client, db_session): + box = Box(name="快速录入箱") + db_session.add(box) + db_session.commit() + + response = create_item( + client, + box.id, + name="袜子", + is_container=False, + submit_action="save_and_add_next", + ) + + assert response.status_code == 303 + assert response.headers["location"] == f"/boxes/{box.id}/items/new" + + +def test_creating_subitem_with_save_and_add_next_returns_to_same_new_subitem_context(client, db_session): + box = Box(name="电子箱") + item = Item(name="配件袋", box=box, is_container=True) + db_session.add_all([box, item]) + db_session.commit() + + response = create_subitem( + client, + item.id, + name="转接头", + submit_action="save_and_add_next", + ) + + assert response.status_code == 303 + assert response.headers["location"] == f"/items/{item.id}/subitems/new"