PythonIDE Docs
中文
简体中文

示例:待办列表

原生列表、搜索、稳定 id 和行级按钮。

适合做任务清单、收藏列表、轻量消息列表。这个样板使用 List + Section + ForEach,支持搜索、新增、完成切换和删除。

#预期效果

运行后会出现可搜索的待办列表,支持新增、完成切换和滑动删除,行身份由 id 保持稳定。

#完整代码

python
import appui

state = appui.State(
    query="",
    next_id=4,
    items=[
        {"id": 1, "title": "Review layout", "done": False},
        {"id": 2, "title": "Check interactions", "done": True},
        {"id": 3, "title": "Polish examples", "done": False},
    ],
)


def visible_items():
    query = state.query.lower().strip()
    if not query:
        return list(state.items)
    return [item for item in state.items if query in item["title"].lower()]


def item_key(item):
    return item["id"]


def add_item():
    item = {"id": state.next_id, "title": f"Task {state.next_id}", "done": False}
    state.items = [item] + list(state.items)
    state.next_id += 1


def set_query(value):
    state.query = value


def toggle_by_id(item_id):
    updated = []
    for item in state.items:
        if item["id"] == item_id:
            updated.append({**item, "done": not item["done"]})
        else:
            updated.append(item)
    state.items = updated


def delete_by_id(item_id):
    state.items = [item for item in state.items if item["id"] != item_id]


def row_view(item):
    def toggle_item():
        toggle_by_id(item["id"])

    def delete_item():
        delete_by_id(item["id"])

    title = item["title"]
    symbol = "checkmark.circle.fill" if item["done"] else "circle"
    color = "systemGreen" if item["done"] else "secondaryLabel"

    return (
        appui.Button(
            action=toggle_item,
            content=appui.HStack([
                appui.Image(system_name=symbol).foreground_color(color),
                appui.Text(title).frame(max_width=appui.infinity, alignment="leading"),
            ], spacing=10),
        )
        .button_style("plain")
        .swipe_actions(actions=[
            appui.Button("Delete", action=delete_item, role="destructive")
        ])
    )


def body():
    rows = appui.ForEach(visible_items(), row_builder=row_view, key=item_key)
    task_list = (
        appui.List([
            appui.Section(f"{len(visible_items())} tasks", [rows])
        ])
        .navigation_title("Todo")
        .searchable(text=state.query, on_change=set_query)
        .toolbar([
            appui.ToolbarItem(
                placement="navigation_bar_trailing",
                content=appui.Button(action=add_item, content=appui.Image(system_name="plus")),
            )
        ])
    )

    return appui.NavigationStack(
        task_list
    )


appui.run(body, state=state)

#可复用点

  • 搜索状态单独保存。
  • 行操作按 id 查找,不依赖筛选后的 index。
  • 动态列表替换整个 state.items,避免原地修改导致刷新语义不清。
  • 新增、切换、删除、搜索都有真实命名回调。