Life OS 文档

文档 / 多文件待办

打开 Markdown

多文件待办

这是可以直接运行的完整多文件应用。入口负责导航,lib/todo.js 负责数据访问,两个页面分别处理列表和详情。列表支持每页 10 条的分页。

运行预览

点击事项可以进入详情,修改状态后返回列表。预览使用相同的多文件加载器与 platform.ui,数据来自独立的模拟数据库。

代码标签中的 JSON 是完整应用包。安装时,将其中的 files 作为 POST /api/appsfiles,另行提供 name 与实际 table_ids

文件说明

app.json

app.json
{
  "name": "待办",
  "entry": "index.js"
}

index.js

index.js
import { createStore } from "./lib/todo.js";
import { showHome } from "./pages/home.js";
import { showDetail } from "./pages/detail.js";

export default async function render(root, platform) {
  const ui = platform.ui;
  root.replaceChildren(ui.loading());
  try {
    const store = await createStore(platform);
    const nav = {
      home: () => showHome(root, ui, store, nav),
      detail: (id) => showDetail(root, ui, store, nav, id),
    };
    await nav.home();
  } catch (error) {
    root.replaceChildren(ui.empty(error.message));
  }
}

lib/todo.js

lib/todo.js
export async function createStore(platform) {
  const dataset = await platform.data.get("todo");
  const data = platform.data;
  return {
    list: (offset) => data.query(dataset.id, { limit: 10, offset }),
    count: () => data.count(dataset.id),
    get: (id) => data.getRecord(dataset.id, id),
    add: (title) => data.insert(dataset.id, { title, done: false }),
    toggle: (row) => data.update(dataset.id, row.id, { done: !row.done }),
  };
}

pages/home.js

pages/home.js
export async function showHome(root, ui, store, nav, offset = 0) {
  root.replaceChildren(ui.loading());
  try {
    const [rows, total] = await Promise.all([store.list(offset), store.count()]);
    const input = ui.input({ placeholder: "新增待办" });
    input.setAttribute("aria-label", "新增待办");
    const add = ui.button("添加", async () => {
      const title = input.value.trim();
      if (!title) return ui.toast("请填写事项名称");
      add.disabled = true;
      try {
        await store.add(title);
        await nav.home();
      } catch (error) {
        ui.toast(error.message);
        add.disabled = false;
      }
    });
    const items = rows.map((row) => {
      const item = ui.row(row.title, row.done ? "已完成" : "未完成");
      item.onclick = () => { void nav.detail(row.id); };
      return item;
    });
    const previous = ui.button("上一页", () => { void showHome(root, ui, store, nav, offset - 10); }, "secondary");
    previous.disabled = offset === 0;
    const next = ui.button("下一页", () => { void showHome(root, ui, store, nav, offset + 10); }, "secondary");
    next.disabled = offset + rows.length >= total;
    root.replaceChildren(ui.page([
      ui.header("待办", "共 " + total + " 项"),
      ui.card(ui.stack([ui.field(input), add])),
      ui.card(ui.stack([
        items.length ? ui.list(items) : ui.empty("还没有事项"),
        previous, next,
      ])),
    ]));
  } catch (error) {
    root.replaceChildren(ui.card([ui.hint(error.message), ui.button("重试", () => { void nav.home(); })]));
  }
}

pages/detail.js

pages/detail.js
export async function showDetail(root, ui, store, nav, id) {
  root.replaceChildren(ui.loading());
  try {
    const row = await store.get(id);
    const toggle = ui.button(row.done ? "改为未完成" : "标记完成", async () => {
      toggle.disabled = true;
      try {
        await store.toggle(row);
        await nav.detail(id);
      } catch (error) {
        ui.toast(error.message);
        toggle.disabled = false;
      }
    });
    root.replaceChildren(ui.page([ui.card(ui.stack([
      ui.title(row.title),
      ui.tag(row.done ? "已完成" : "未完成"),
      toggle,
      ui.button("返回列表", () => { void nav.home(); }, "secondary"),
    ]))]));
  } catch (error) {
    root.replaceChildren(ui.card([
      ui.hint(error.message),
      ui.button("返回列表", () => { void nav.home(); }, "secondary"),
    ]));
  }
}

使用自己的数据

安装前创建 title: stringdone: boolean 两个字段的数据集,并确认 slug 为 todo。否则修改 lib/todo.js 中的 slug,再将数据集 ID 绑定到应用。

示例的 nav.homenav.detail 是应用自己定义的函数,并非宿主 API。它说明了如何用多文件组织页面,不依赖尚未提供的页面路由器。