# 待办示例

这个完整应用演示新增事项、查询未完成事项、统计条数和修改完成状态。预览使用独立的 todo 数据集，可以直接操作；切换「预览 / 代码」保留当前预览状态，刷新文档会恢复初始数据。

## 数据准备

安装到桌面前，准备 slug 为 `todo` 的数据集，字段为 `title: string` 和 `done: boolean`，并将它的实际 ID 放入应用的 `table_ids`。如果现有数据集的 slug 不同，请修改代码中的 `get("todo")`。

```js preview height=540 file="index.js"
export default async function render(root, platform) {
  const ui = platform.ui;
  let todos;
  try {
    todos = await platform.data.get("todo");
  } catch {
    root.replaceChildren(ui.empty("请先创建并绑定 slug 为 todo 的待办数据集。"));
    return;
  }
  const input = ui.input({ placeholder: "例如 取快递" });
  input.setAttribute("aria-label", "新增待办");
  const list = ui.list([]);
  const hint = ui.hint("");
  let onlyOpen = true;
  let request = 0;
  const add = ui.button("添加", async () => {
    const title = input.value.trim();
    if (!title) return ui.toast("请填写事项名称");
    add.disabled = true;
    try {
      await platform.data.insert(todos.id, { title, done: false });
      input.value = "";
      await refresh();
    } catch (error) {
      ui.toast(error.message);
    } finally {
      add.disabled = false;
    }
  });
  const tabs = ui.tabs([
    { id: "open", label: "未完成" },
    { id: "all", label: "全部" },
  ], { value: "open", onChange: (id) => { onlyOpen = id === "open"; void refresh(); } });
  root.replaceChildren(ui.page([
    ui.card(ui.stack([ui.title("待办"), ui.field(input), add])),
    ui.card(ui.stack([tabs, hint, list])),
  ]));

  async function refresh() {
    const current = ++request;
    const where = onlyOpen ? { done: false } : {};
    try {
      const [rows, total] = await Promise.all([
        platform.data.query(todos.id, { where, limit: 50 }),
        platform.data.count(todos.id, { where }),
      ]);
      if (current !== request) return;
      hint.textContent = "共 " + total + " 项，显示最近 " + rows.length + " 项";
      const items = rows.map((row) => {
        const item = ui.row(row.title, row.done ? "已完成" : "未完成");
        item.onclick = async () => {
          item.disabled = true;
          try {
            await platform.data.update(todos.id, row.id, { done: !row.done });
            await refresh();
          } catch (error) {
            ui.toast(error.message);
            item.disabled = false;
          }
        };
        return item;
      });
      list.replaceChildren(...(items.length ? items : [ui.empty("没有符合条件的事项")]));
    } catch (error) {
      if (current === request) hint.textContent = error.message;
    }
  }
  await refresh();
}
```

## 实现要点

- 筛选通过 `query(id, { where })` 完成，不先加载整份数据再筛选。
- `query` 返回数组，`count` 单独返回匹配总数。示例展示最近 50 项，更多记录请使用分页。
- 写入期间禁用按钮，并使用 `try/catch` 展示错误。
- 请求序号避免快速切换筛选时，旧请求覆盖最新结果。

需要列表、详情等多个页面时，参考[多文件待办](/doc/examples/multi-file)。
