做一个待办应用
这篇不引入新概念,只把前面学过的东西拼成一个能用的待办清单。
学完能做什么: 在 Studio 里对照粘贴,得到列表 + 详情 + 刷新后数据还在的应用;启动时尝试拉一句提示,失败则用本地文案。
前置: 数据、列表与事件。导入名以 页面路由 为准:@blueos.app.appmanager.router(部分旧示例写成 @blueos.app.router)。
数据形状固定为 { id, title, done }。没有后端、没有登录。
1. 工程结构
Section titled “1. 工程结构”在 Studio 新建工程后,改成:
src/ app.ux manifest.json pages/ Todos/index.ux TodoDetail/index.ux2. manifest.json
Section titled “2. manifest.json”{ "package": "com.example.todos", "name": "待办", "icon": "/assets/images/logo.png", "versionName": "1.0", "versionCode": 1, "appCategory": ["utilities"], "config": { "designWidth": 466 }, "features": [ { "name": "blueos.storage.storage" }, { "name": "blueos.network.fetch" } ], "router": { "entry": "Todos", "pages": { "Todos": { "component": "index" }, "TodoDetail": { "component": "index" } } }}图标路径改成你工程里真实的 icon。存储见 K-V 数据存储,请求见 数据请求。
3. app.ux
Section titled “3. app.ux”<script> /**manifest**/ export default {}</script>不要删除 /**manifest**/。
4. 列表页 src/pages/Todos/index.ux
Section titled “4. 列表页 src/pages/Todos/index.ux”<script> import router from '@blueos.app.appmanager.router' import storage from '@blueos.storage.storage' import fetch from '@blueos.network.fetch'
export default { data: { tip: '今天也值得认真对待', newTitle: '', todos: [], }, onInit() { this.loadTodos() this.loadTip() }, onShow() { this.loadTodos() }, loadTodos() { storage.get({ key: 'todos', success: (data) => { this.todos = Array.isArray(data) ? data : [] }, fail: () => { this.todos = [] }, }) }, saveTodos() { storage.set({ key: 'todos', value: this.todos, fail: () => {}, }) }, loadTip() { fetch.fetch({ url: 'https://httpbin.org/json', responseType: 'json', success: (response) => { const title = response.data && response.data.slideshow && response.data.slideshow.title if (title) { this.tip = title } }, fail: () => {}, }) }, onTitleChange(evt) { this.newTitle = evt.value }, addTodo() { const title = (this.newTitle || '').trim() if (!title) { return } const next = this.todos.concat([ { id: String(Date.now()), title: title, done: false }, ]) this.todos = next this.newTitle = '' this.saveTodos() }, openDetail(item) { router.push({ uri: '/TodoDetail', params: { id: item.id }, }) }, }</script>
<template> <div class="flex flex-col w-full"> <text class="text-white text-2xl">{{ tip }}</text> <div class="flex items-center w-full mt-10"> <input class="title-input text-white" type="text" value="{{newTitle}}" placeholder="新的待办" onchange="onTitleChange" /> <input class="text-white" type="button" value="添加" onclick="addTodo" /> </div> <text if="{{todos.length === 0}}" class="text-white mt-10">还没有待办</text> <list class="w-full mt-10"> <list-item type="todo" for="{{(index, item) in todos}}" tid="id" onclick="openDetail(item)"> <div class="flex items-center w-full"> <text class="text-white">{{ item.title }}</text> </div> </list-item> </list> </div></template>
<style>@tailwind utilities;
.title-input { flex: 1;}</style>onShow 会在每次回到列表时重新读取存储(详情页返回后才会看到最新状态)。
无网时 loadTip 走 fail,顶部保持默认文案。这是教学预期,不是缺陷。
5. 详情页 src/pages/TodoDetail/index.ux
Section titled “5. 详情页 src/pages/TodoDetail/index.ux”路由参数会挂到页面实例上,类型是字符串。用 params.id 时,详情页通过 this.id 读取,所以 id 也要出现在 data 里。
<script> import router from '@blueos.app.appmanager.router' import storage from '@blueos.storage.storage'
export default { data: { id: '', title: '', done: false, todos: [], }, onInit() { this.loadAndShow() }, loadAndShow() { storage.get({ key: 'todos', success: (data) => { const todos = Array.isArray(data) ? data : [] this.todos = todos const found = todos.filter((item) => item.id === this.id)[0] if (found) { this.title = found.title this.done = found.done } }, fail: () => { this.todos = [] }, }) }, saveTodos() { storage.set({ key: 'todos', value: this.todos, fail: () => {}, }) }, toggleDone() { const done = !this.done const next = this.todos.map((item) => { if (item.id === this.id) { return { id: item.id, title: item.title, done: done } } return item }) this.todos = next this.done = done this.saveTodos() }, removeTodo() { this.todos = this.todos.filter((item) => item.id !== this.id) storage.set({ key: 'todos', value: this.todos, success: () => { router.back() }, fail: () => {}, }) }, }</script>
<template> <div class="flex flex-col items-center w-full"> <text class="text-white text-3xl">{{ title }}</text> <text class="text-white mt-10">{{ done ? '已完成' : '未完成' }}</text> <input class="text-white mt-10" type="button" value="切换完成" onclick="toggleDone" /> <input class="text-white mt-10" type="button" value="删除" onclick="removeTodo" /> </div></template>
<style>@tailwind utilities;</style>从列表点进详情,切换完成或删除后返回。杀进程再进应用,列表应仍在(存储成功的前提下)。
删除后要等存储写入成功再返回,否则列表可能读到旧数据。
- 两页都要写进
router.pages;存储和 fetch 要写进features。 - 更新列表用
this.todos = next,不要只改this.todos[i].done。 - 每个异步 API 都带
fail;请求失败就保留本地tip。
下一篇:常见坑