跳转到内容

做一个待办应用

这篇不引入新概念,只把前面学过的东西拼成一个能用的待办清单。

学完能做什么: 在 Studio 里对照粘贴,得到列表 + 详情 + 刷新后数据还在的应用;启动时尝试拉一句提示,失败则用本地文案。

前置: 数据、列表与事件。导入名以 页面路由 为准:@blueos.app.appmanager.router(部分旧示例写成 @blueos.app.router)。

数据形状固定为 { id, title, done }。没有后端、没有登录。

在 Studio 新建工程后,改成:

src/
app.ux
manifest.json
pages/
Todos/index.ux
TodoDetail/index.ux
{
"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 数据存储,请求见 数据请求

<script>
/**manifest**/
export default {}
</script>

不要删除 /**manifest**/

<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 会在每次回到列表时重新读取存储(详情页返回后才会看到最新状态)。

无网时 loadTipfail,顶部保持默认文案。这是教学预期,不是缺陷。

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

下一篇:常见问题