开发 Todo List 小程序
收藏我的收藏
简介
抖音小程序是可以在抖音、抖音极速版、抖音火山版、今日头条、今日头条极速版 App 中运行的小程序。一套代码,可以支持 Android、iOS 双端适配。
本教程将带你从零开始创建一个 Todo 小程序,你将快速了解如何调用小程序组件、小程序 API、以及如何让小程序适配移动端。
该小程序将实现以下功能:
- •用户登录并获取用户的基本信息。
- •用户可创建和更新待办事项列表。
开发路径说明
小程序开发发布流程如下:
- 1.入驻开发者平台。
- 3.创建小程序项目。
- 4.全局配置。
- 5.开发页面。
- 6.预览与调试。
- 7.发布小程序。
其中,入驻过程需要多个步骤,且入驻和创建小程序需经过平台审核。为方便快速体验开发流程,建议:
- 1.使用测试号创建小程序项目。
- 2.全局配置。
- 3.开发页面。
- 4.预览与调试。
- 5.待入驻开发者平台、创建小程序后,再发布小程序。
预览效果
使用抖音、抖音极速版、今日头条、今日头条极速版 App 扫描以下代码,体验 Todo 小程序 Demo。
可运行的示例代码
视频教程
创建小程序项目
安装 IDE
在 IDE 中新建小程序项目
- 1.打开 IDE,用邮箱、手机、抖音扫码都可以完成登录。
- 2.在 IDE 左侧导航栏点击「小程序」,在「小程序」界面点击「新建」。
- 3.在「新建项目」页面配置小程序项目信息,然后点击「新建」。
配置项 | 说明 |
项目目录 | 选择本地的项目目录。 |
项目名称 | 输入小程序的项目名称。项目名称请遵守以下要求:
|
AppID |
注意 使用测试号开发的小程序无法上传到抖音开放平台。 |
后端服务 | 可以选择自建后端或使用抖音云开发。
|
开发语言 | 推荐选择 JavaScript。 |
项目模板 | 选择空白模板。 也可以根据实际需求选择其它模板。 |
创建完成后,即可在 IDE 中看到小程序的目录结构。
小程序目录结构
抖音小程序以 MVVM(Model–view–viewmodel) 的方式进行开发,通过状态变更来更新视图。
目录结构
小程序包含一个描述整体程序的
app
和多个描述各自页面的 page
。. ├── app.js # 小程序逻辑 ├── app.json # 小程序公共配置 ├── app.ttss # 小程序公共样式表 ├── project.config.json # 项目配置 └── pages # 页面 └── pageA # 页面 A ├── pageA.ttss # 页面 A 样式表 ├── pageA.js # 页面 A 逻辑 ├── pageA.json # 页面 A 配置 └── pageA.ttml # 页面 A 结构
根目录
一个小程序主体部分由 1 个项目配置文件和 3 个全局配置文件组成,必须放在项目的根目录下。
文件 | 必需 | 作用 |
是 | 小程序逻辑 | |
是 | 小程序公共配置 | |
否 | 小程序公共样式表 | |
是 | 项目配置 |
页面
上述目录结构中的
pages/
可以根据项目结构灵 活调整,仅需要保证其与 app.json
文件内 pages
字段的配置对应。一个小程序页面由 4 个文件组成:
注意
为了方便开发者减少配置项,描述页面的 4 个文件必须具有相同的路径与文件名。
允许上传的文件
在项目目录中,以下文件会经过编译,因此上传之后无法直接访问
.js
、.json
、.ttml
、.ttss
(其中 .ttml
和 .ttss
文件仅针对在 app.json
中配置了的页面)。除此之外,只能上传后缀名在白名单内的文件,白名单外的文件可以在开发工具访问,但无法上传。具体白名单包含:png、jpg、jpeg、gif、svg、cer、mp3、aac、m4a、mp4、ttf、otf、eot、woff。
注意:后缀名在白名单内的文件和
.js
文件无论是否被引用都会被打包,.json、.ttml 和 .ttss 文件只有被引用时才会被打包。小程序代码构成
小程序项目中单个页面会依赖不同类型的文件:
- •
.json
后缀的 JSON
配置文件- •
.ttml
后缀的 TTML
模板文件- •
.ttss
后缀的 TTSS
样式文件- •
.js
后缀的 JS
脚本文件接下来我们分别看看这 4 种文件的作用。
JSON 配置
JSON 是一种数据格式,并不是编程语言,在小程序中,JSON 扮演着静态配置的角色。
对于一个包含 index 页面的小程序而言,在项目的根目录有一个
app.json
和 project.config.json
,此外在 pages/index
目录下还有一个 index.json
。小程序配置 app.json
app.json
是小程序的全局配置,包括了小程序的所有页面路径、界面表现、网络超时时间、底部 tab 等。一般包含如下内容:{ "pages": ["pages/index/index", "pages/logs/logs"], "window": { "backgroundTextStyle": "light", "navigationBarBackgroundColor": "#fff", "navigationBarTitleText": "TikTok", "navigationBarTextStyle": "black" } }
这个配置各项的含义如下:
- •pages 字段:用于描述小程序所有页面路径,让客户端知晓页面文件的目录定义。
- •
window
字段:定义小程序所有页面的顶部背景颜色,文字颜色等。工具配置 project.config.json
通常在使用工具时,可根据个人喜好进行个性化配置(如界面颜色、编译设置等)。更换电脑重新安装工具时,载入项目即可自动恢复配置。
小程序开发者工具在每个项目的根目录生成 project.config.json 文件,记录所有个性化配置(包括编辑器颜色、代码上传自动压缩等)。重新安装工具或更换电脑后,载入项目代码包即可自动恢复配置。
页面配置 page.json
page.json
表示 pages/logs
目录下的 logs.json
等小程序 页面相关配置。如果你整个小程序的风格是蓝色调,那么你可以在
app.json
里边声明顶部颜色是蓝色即可。实际情况可能不是这样,可能你小程序里边的每个页面都有不一样的色调来区分不同功能模块,因此我们提供了 page.json
,让开发者可以独立定义每个页面的一些属性,例如刚刚说的顶部颜色、是否允许下拉刷新等等。JSON 语法
这里说一下小程序里 JSON 配置的一些注意事项。
JSON 文件包裹在大括号 {} 中,通过 key-value 结构表达数据。开发者常见错误包括:未用双引号包裹 key 值或误用单引号。
JSON 的值只能是以下几种数据格式,其他任何格式都会触发报错,例如 JavaScript 中的 undefined。
- •数字,包含浮点数和整数
- •字符串,字符串值需要包裹在双引号中
- •Boolean 值,true 或者 false
- •数组,需要包裹在方括 号中 []
- •对象,需要包裹在大括号中 {}
- •Null
还需要注意的是 JSON 文件中无法使用注释,添加注释将触发系统报错。
TTML 模板
网页编程采用 HTML+CSS+JS 组合:HTML 描述页面结构,CSS 定义样式,JS 处理用户交互。 同样道理,在小程序中也有同样的角色,其中 TTML 充当的就是类似 HTML 的角色。比如以下的内容:
<view class="container"> <view class="userinfo"> <button tt:if="{{!hasUserInfo && canIUse}}">获取头像昵称</button> <block tt:else> <image src="{{userInfo.avatarUrl}}" background-size="cover"></image> <text class="userinfo-nickname">{{userInfo.nickName}}</text> </block> </view> <view class="usermotto"> <text class="user-motto">{{motto}}</text> </view> </view>
和 HTML 非常相似,TTML 由标签、属性等构成。但是也有很多不一样的地方,具体差异如下:
和 HTML 的差异 | 说明 |
标签名字不一样 | 往往写 HTML 的时候,经常会用到的标签是 div 、p 、span ,开发者在写一个页面的时候可以根据这些基础的标签组合出不一样的组件,例如日历、弹窗等等。换个思路,既然大家都需要使用这些组件,为什么我们不能把这些常用的组件包装起来,来大大提高我们的开发效率呢?从上述的例子可以看到,小程序的 TTML 用的标签是 view 、button 、text 等等,这些标签就是小程序给开发者包装好的基本能力,我们还提供了地图、视频、音频等组件能 力。 |
多了一些 tt:if 这样的属性以及 {{ }} 这样的表达式 | 在网页的一般开发流程中,我们通常会通过 JS 操作 DOM (对应 HTML 的描述产生的树),以引起界面的一些变化响应用户的行为。 例如,用户点击某个按钮的时候,JS 会记录一些状态到 JS 变量里边,同时通过 DOM API 操控 DOM 的属性或者行为,进而引起界面一些变化。 当项目越来越大的时候,代码会充斥着非常多的界面交互逻辑和程序的各种状态变量,显然这不是一个很好的开发模式,因此就有了 MVVM 的开发模式(例如 React, Vue),提倡把渲染和逻辑分离。简单来说就是不要再让 JS 直接操控 DOM,JS 只需要管理状态即可,然后再通过一种模板语法来描述状态和界面结构的关系即可。 小程序框架采用此思路实现界面显示。例如在界面展示"Hello World"字符串时: TTML 是这么写 :
JS 只需要管理状态即可:
通过 {{ }} 的语法把一个变量绑定到界面上,我们称为数据绑定。仅仅通过数据绑定还不能完整地描述状态和界面的关系,还需要 if/else , for 等控制能力,在小程序里边,这些控制能力都用 tt: 开头的属性来表达。 |
TTSS 样式
TTSS 具有 CSS 大部分的特性,小程序在 TTSS 也做了一些扩充和修改。
新增了尺寸单位。在写 CSS 样式时,开发者需要考虑到手机设备的屏幕会有不同的宽度和设备像素比,采用一些技巧来换算一些像素单位。TTSS 支持 rpx 单位,该单位由小程序底层自动完成尺寸换算。因采用浮点运算,换 算结果可能存在微小数值偏差。
提供了全局样式和局部样式。和前边
app.json/page.json
的概念类似,你可以写一个 app.ttss
作为全局样式,会作用于当前小程序的所有页面,局部页面样式page.ttss
仅对当前页面生效。此外 TTSS 仅支持部分 CSS 选择器。
JS 逻辑交互
一个服务仅仅只有界面展示是不够的,还需要和用户做交互:响应用户的点击、获取用户的位置等等。在小程序里边,我们就通过编写 JS 脚本文件来处理用户的操作。
<view>{{ msg }}</view> <button bindtap="clickMe">点击我</button>
点击
button
时,需要将界面的 msg
显示为 "Hello World"
,通过在 button
上声明 bindtap
属性,并在 JS 文件中使用 clickMe
方法响应该点击操作:Page({ clickMe: function() { this.setData({ msg: "Hello World" }) } })
此外你还可以在 JS 中调用小程序提供的丰富 API,利用这些接口可以很方便地调用小程序提供的能力,例如获取用户信息、本地存储、支付等。更多信息可参考小程序 API 文档。
全局配置
在上一章中,你成功创建了你的第一个小程序项目,接下来将了解这个小程序的全局配置文件的含义和用法。
小程序包含 4 个全局配置文件类型:
- •工程配置文件
project.config.json
- •全局配置文件
app.json
- •全局样式文件
app.ttss
- •逻辑文件
app.js
工程配置文件 project.config.json
project.config.json
是小程序项目的工程配置文件,包含小程序的 appid
、projectname
(项目名称)和 setting
(项目设置)信息。详情请参见 project.config.json 配置介绍。{ "setting": { "urlCheck": true, "es6": true, "postcss": true, "minified": true, "newFeature": true, "autoCompile": true }, "appid": "testAppId", "projectname": "空白模板", "douyinProjectType": "native" }
project.config.json
在创建项目时会自动生成,你也可以根据需要进行修改和配置。重新安装开发工具或更换设备时,载入项目代码包后 IDE 会自动恢复原有配置
全局配置文件 app.json
属性 | 说明 |
pages | 配置小程序的所有页面路径。配置中的第一个页面路径就是小程序启动时展示的第一个页面。 |
window | 设置小程序的页面样式,包括状态栏、导航栏、标题、背景色等。 |
{ "pages": ["pages/index/index"], "window": { "backgroundTextStyle": "light", "navigationBarBackgroundColor": "#fff", "navigationBarTitleText": "Mini Program", "navigationBarTextStyle": "black" } }
本教程中,需要把小程序的导航栏文案改为“Todo Demo”,例如需要将以下配置项修改:
"window":{ "backgroundTextStyle":"light", "navigationBarBackgroundColor": "#fff", "navigationBarTitleText": "Mini Program", "navigationBarTextStyle":"black" }
改为如下配置示例:
"window":{ "backgroundTextStyle":"light", "navigationBarBackgroundColor": "#fff", "navigationBarTitleText": "Todo Demo", "navigationBarTextStyle":"black" }
保存之后,在 IDE 的模拟器中预览,小程序的导航栏文案已经变为“Todo Demo”。
全局样式文件 app.ttss
app.ttss
是小程序的全局样式文件,作用于小程序的每一个页面,用于描述 TTML 的组件样式。注意
- •具体样式设计和类名需根据实际需求调整。
- •
app.ttss
的优先级低于具体页面的 page.ttss
文件。在本教程中,我们需要设置页面以及添加 Todo 的按钮和图标的某些样式,请将如下代码写入
app.ttss
文件中。你也可以根据喜好尝试调整这些样式。page { flex: 1; display: flex; background: #E7E7E7; } .add-button { display: flex; align-items: center; justify-content: center; background: none; color: #FFF; border: none; width: 300rpx; font-size: 52rpx; color: #000000; height: 120rpx; color: #FFF; background-color: #FE2C55; } .add-button::after { border: none; }
逻辑文件 app.js
app.js
文件是小程序的逻辑文件,主要负责创建全局小程序应用实例(App()
),配置生命周期,声明全局数据、调用 API 等。整个小程序只有一个
App
实例,是全部页面共享的。开发者可以通过 getApp
方法获取到全局唯一的 App
实例,获取 App
上的数据或调用开发者注册在 App
上的函数。关于 app.js
的详细说明,请参考 App。App({ // todo list数组,如果storage没有缓存默认读取这个值 todos: [ { text: "学习 Javascript", completed: true, }, { text: "学习 ES2016", completed: true, }, { text: "学习 抖音小程序", completed: false, }, ], userInfo: null, /** * @description: 调用tt.getUserProfile获取用户信息,返回一个Promise对象 * @return {Promise} */ getUserProfile() { return new Promise((resolve, reject) => { if (this.userInfo) { resolve(this.userInfo); return; } tt.getUserProfile({ success: (res) => { console.log(res); this.userInfo = res.userInfo; resolve(this.userInfo); }, fail: (err) => { reject(err); }, }); }); }, onLaunch() { try { const storage = tt.getStorageSync("todos"); if (Array.isArray(storage)) { this.todos = storage; } } catch (err) { console.log(err); } }, });
开发页面
了解程序与页面
小程序启动之后,系统会执行在
app.js
中定义的 App
实例的 onLaunch
回调:App({ onLaunch() { try { const storage = tt.getStorageSync("todos"); if (Array.isArray(storage)) { this.todos = storage; } } catch (err) { console.log(err); } }, });
整个小程序的所有页面共享唯一的 App 实例。小程序由多个功能页面组成,支持页面间跳转操作,后文「页面」均特指小程序页面。
了解页面目录结构
Todo Demo 小程序项目中包含两个页面,todos(更新 Todo 状态)和 add-todo(添加 Todo)。
两个页面都位于 pages 目录下,且路径需要在 app.json 中声明。
这两个页面分别包含以下文件:
todos 页面 | add-todo 页面 | 说明 |
todos.js | add-todo.js | |
todos.json | add-todo.json | 实现页面配置。 页面的配置文件不是必须的。
app.json 中的配置。
app.json 中 window 下的同名配置项。 |
todos.ttml | add-todo.ttml | 实现页面结构。 |
todos.ttss | add-todo.ttss | 实现页面中的组件样式。 |
了解页面处理逻辑
在明确页面结构和生成过程的基础上,接下来阐述页面的核心处理逻辑。页面的处理逻辑在
page.js
中。const app = getApp() Page({ data: { text: "This is page data." }, onLoad: function(options) { // 页面创建时执行 }, // 事件响应函数 viewTap: function() { this.setData({ text: 'Set some data for updating view.' }, function() { // this is setData callback }) },
page.js
文件主要使用 Page 构造器来构建页面。Page 是一个页面构造器,定义了页面的初始数据、生命周期函数(如 onLoad,onReady,onShow 等)以及事件处理函数等。这些函数和数据共同组成了页面的处理逻辑。详情请参考页面。页面加载完成后会触发
onLoad
回调,开发者可在该回调函数中处理加载成功后的业务逻辑。开发 todos 页面
绘制 todos 页面结构
todos.ttml
实现页面结构,todos.ttss
描述组件样式。将以下代码复制到
todos.ttml
文件中。<view class="page-todos"> <view class="user"> <view class="nickname">{{user.nickName && user.nickName + '的' || '我的'}}记事本</view> <button tt:if="{{!user}}" class="login-btn" bindtap="getUserProfile">登录</button> <image tt:else class="avatar" src="{{user.avatarUrl || '../../assets/logo.svg'}}" background-size="cover"></image> </view> <view class="todo-items"> <checkbox-group class="todo-items-group"> <label data-index="{{index}}" bindtap="toggleState" tt:for="{{todos}}" tt:for-item="item" class="todo-item {{item.completed ? 'checked' : ''}}" tt:key="{{index}}"> <checkbox color="#FE2C55" class="todo-item-checkbox" value="{{item.text}}" checked="{{item.completed}}" /> <text class="todo-item-text">{{item.text}}</text> <image class="delete" catchtap="deleteItem" data-index="{{index}}" src="../../assets/delete.svg"></image> </label> </checkbox-group> </view> <view class="todo-footer"> <button class="add-button" hover-class="none" bindtap="addTodo"> <image src="../../assets/add.svg" style="width: 52rpx; height: 52rpx"></image> </button> </view> </view>
将以下代码复制到
todos.ttss
文件中。.page-todos { display: flex; flex-direction: column; width: 100%; max-height: 100vh; } .login-btn { margin-top: 40rpx; background-color: #FE2C55; color: #FFFFFF; } .login-btn::after { border: none; } .user { display: flex; flex-shrink: 0; padding: 30px; color: #FFF; flex-direction: column; align-items: center; } .avatar { width: 130rpx; height: 130rpx; border-radius: 50%; background-color: #FFF; align-self: center; margin-top: 40rpx; } .nickname { /* padding-top: 40rpx; */ text-align: center; font-size: 40rpx; font-weight: 100; color: #000000; } .todo-items { flex-grow: 1; font-size: 34rpx; padding: 50rpx 60rpx; /* color: #000000; */ overflow: auto; } .todo-items-group { display: flex; flex-direction: column; } .todo-item { position: relative; margin-bottom: 38rpx; height: 104rpx; background-color: #FFFFFF; overflow: hidden; text-overflow: ellipsis; transition: border 0.2s; display: flex; align-items: center; font-size: 30rpx; } .todo-item:last-child { margin-bottom: 0; } .todo-item-checkbox { /* display: none; */ margin-left: 32rpx; margin-right: 24rpx; } .checked .todo-item-text { text-decoration: line-through; font-size: 30rpx; } .checked.todo-item::after { opacity: 1; } .todo-footer { flex-shrink: 0; padding: 50rpx 0 100rpx; font-size: 48rpx; font-weight: 200; text-align: center; } .todo-item .delete { position: absolute; right: 20px; top: 50%; transform: translateY(-50%); color: #000; height: 30rpx; width: 30rpx; }
在
todos.ttml
文件中,使用 <view/>
、<image/>
、<text/>
、<button/>
、<label/>
、<checkbox/>
来搭建页面结构,通过 Mustache 语法({{}})绑定 todos 数据。我们定义了三个页面模块:
- 1.user 模块用于实现用户信息功能
- 2.todo-items 模块用于管理已有 Todo 的完成状态
- 3.todo-footer 模块负责实现添加 Todo 按钮功能
以用户信息模块为例,
todos.ttml
文件中通过<view class="user">
实现以下功能:- 1.Todo Demo 的标题(xxx 的记事本)
- 2.登录按钮
- 3.登录后自动显示用户头像和用户名
<view class="user"> <view class="nickname">{{user.nickName && user.nickName + '的' || '我的'}}记事本</view> <button tt:if="{{!user}}" class="login-btn" bindtap="getUserProfile">登录</button> <image tt:else class="avatar" src="{{user.avatarUrl || '../../assets/logo.svg'}}" background-size="cover"></image> </view>
class="user"
为用户信息的类,在 todos.ttss
中通过以下代码描述了该类的样式。.user { display: flex; flex-shrink: 0; padding: 30px; color: #fff; flex-direction: column; align-items: center; }
user
模块包含登录状态判断逻辑和nickname
变量:- •如果当前用户未登录,则展示「我的记事本」和「登录」按钮。登录功能通过设置
button
组件的open-type="getUserProfile"
属性实现,样式定义在todos.ttss
的.login-btn
选择器中。登录按钮的 bindtap="getUserProfile"
, bindtap
是所有组件绑定事件的方法,这里代表点击登录按钮时会调用 tt.getUserProfile
。- •如果当前用户已登录,则展示「{{nickname}}的记事本」和用户头像。通过
nickname
数据绑定显示实际用户名。用户头像通过 image 组件 实现,src
为图片资源地址。实现 todos 页面的处理逻辑
页面搭建完成后,需要定义该页面的变量及函数方法。我们需要实现的效果是:
- 1.在 Todo 页面点击登录,用户授权同意后,调用
tt.getUserProfile
接口获取用户昵称和头像信息。- 2.点击已有的未完成的 Todo ,完成 Todo。
- 3.点击 +,进入添加 Todo 页面。添加 Todo 页面的处理逻辑,请参见实现 add-todo 页面的处理逻辑。
- 4.在 Todo 项右侧点击 x, 删除 Todo 项。
目标效果:
请将以下代码复制到
todos.js
文件中。// 获取全局 app 实例 const app = getApp(); Page({ // 声明页面数据 data: { todos: [], }, // 监听生命周期回调 onLoad onLoad() {}, // 监听生命周期回调 onShow onShow() { // 设置全局数据到当前页面数据 this.setData({ todos: app.todos }); }, // 事件处理函数 toggleState(e) { // 修改全局数据 const { index } = e.currentTarget.dataset; app.todos[index].completed = !app.todos[index].completed; this.setData({ todos: app.todos }); tt.setStorage({ key: "todos", data: app.todos, }); }, /** * @description: 获取用户数据 * @return {Promise<void>} */ async getUserProfile() { try { const user = await app.getUserProfile(); this.setData({ user, }); } catch (err) { console.error(err); } }, /** * @description: 跳转到添加待办项的页面 * @return {void} */ addTodo() { // 进行页面跳转 tt.navigateTo({ url: "../add-todo/add-todo" }); }, /** * @description: 删除一个待办项 * @param {*} e 点击事件 * @return {void} */ deleteItem(e) { // 获取删除待办项的index索引 const { index } = e.currentTarget.dataset; // 从app.todos中删除index索引对应的item app.todos.splice(index, 1); // 调用setData重新设置todos this.setData({ todos: app.todos }); // 设置缓存 tt.setStorage({ key: "todos", data: app.todos, }); }, });
该代码通过调用
tt.setStorage
等接口实现待办事项列表功能,包含任务状态切换、用户信息获取、新增任务及删除任务等功能模块。Page()
是小程序页面的代码。包括:数据声明
data:{}
,用于声明页面的数据。data: {},
生命周期回调
onLoad()
:监听页面加载时触发。onLoad() { },
onShow()
:该回调函数在页面显示时触发,主要实现以下功能:通过 setData() 方法将全局数据同步到页面数据中。
onShow() { // 设置全局数据到当前页面数据 this.setData({ todos: app.todos }); },
事件处理函数
toggleState(e) { // 修改全局数据 const {index} = e.currentTarget.dataset; app.todos[index].completed = !app.todos[index].completed; this.setData({ todos: app.todos }); tt.setStorage({ key: 'todos', data: app.todos, }); },
async getUserProfile() { // 获取用户信息并存储数据 try { const user = await app.getUserProfile(); this.setData({ user, }); } catch(err) { console.error(err); } },
通过
tt.navigateTo
方法实现页面跳转至../add-todo/add-todo
路径。addTodo() { // 进行页面跳转 tt.navigateTo({ url: "../add-todo/add-todo" }); },
deleteItem(e) { // 获取删除待办项的index索引 const { index } = e.currentTarget.dataset; // 从app.todos中删除index索引对应的item app.todos.splice(index, 1); // 调用setData重新设置todos this.setData({ todos: app.todos }); // 设置缓存 tt.setStorage({ key: 'todos', data: app.todos, }); },
开发 add-todo 页面
绘制 add-todo 页面结构
add-todo.ttml
实现页面结构,add-todo.ttss
描述组件样式。将以下代码复制到
add-todo.ttml
文件中。<view class="page-add-todo"> <view class="add-todo"> <input class="add-todo-input" placeholder="添加待办项" onBlur="onBlur" value="{{inputValue}}" /> </view> <view class="todo-footer"> <button class="add-button" hover-class="none" bindtap="add"> <text class="add-icon">+</text> <text>添加</text> </button> </view> </view>
将以下代码复制到
add-todo.ttss
文件中。.page-add-todo { display: flex; flex: 1; flex-direction: column; } .add-todo { padding: 40px; flex-grow: 1; display: flex; justify-content: center; align-items: center; } .add-todo-input { display: block; font-size: 50rpx; font-weight: 100; padding: 5px 5px; background: none; border: none; border-bottom: 1px solid #dfdfdf; color: #0effd6; width: 100%; } .todo-footer { padding: 50rpx 0 100rpx; font-size: 48rpx; font-weight: 200; text-align: center; }
在
add-todo.ttml
文件中,使用 <view/>
、<input/>
、<text/>
、<button/>
来搭建页面结构。定义以下两个页面模块:
- 1.add-todo 模块实现填写 Todo 描述
- 2.todo-footer 模块实现添加 Todo 按钮
以下以页面结构为例解析代码。
add-todo.ttml
的<view class="add-todo"> </view>
用于填写 Todo 描述,以下代码实现了:- 1.设置 placeholder 为「添加待办项」
- 2.组件接收用户输入的 Todo 描述
<view class="add-todo"> <input class="add-todo-input" placeholder="添加待办项" onBlur="onBlur" value="{{inputValue}}" /> </view>
class="add-todo"
用于填写 Todo 的类,在 add-todo.ttss
中通过以下代码描述了该类的样式。.add-todo { padding: 40px; flex-grow: 1; display: flex; justify-content: center; align-items: center; }
实现 add-todo 页面的处理逻辑
页面搭建完成后,需要定义该页面的变量及函数方法。我们需要实现的效果是:
- 1.在添加 Todo 页面添加待办项。
- 2.点击 + 添加,将新的待办项添加到 Todo 页面。
目标效果:
请将以下代码复制到
add-todo.js
文件中。const app = getApp(); Page({ data: { inputValue: "", }, /** * @description: input输入框失焦回调 * @param {*} e 回调事件 * @return {void} */ onBlur(e) { // 设置输入框中的输入值 this.setData({ inputValue: e.detail.value, }); }, /** * @description: 把输入框中的值插入app.todos,同时设置缓存 * @return {void} */ add() { app.todos.push({ text: this.data.inputValue, compeleted: false, }); tt.setStorage({ key: "todos", data: app.todos, }); // 添加完毕,返回上一页 tt.navigateBack(); }, });
这段代码实现了输入待办项并将其添加到列表页面的功能。
Page()
是小程序页面的代码。包括:数据声明
data:{}
,用于声明页面的数据,即在用户输入待办项后把输入的值设置为数据中的 inputValue 属性。data: { inputValue: '', },
生命周期回调
setData() 方法将文本框当前值存储到页面数据,以便后续使用。
监听页面输入框的 onBlur 事件,当文本框失去焦点时触发。this 指向当前页面实例,setData() 方法用于设置数据,通过事件对象获取当前文本框的值并存储为 inputValue 参数。
onBlur(e) { this.setData({ inputValue: e.detail.value, }); },
事件处理函数
当用户点击「+ 添加」按钮时,该函数执行以下操作:将输入框中的文本添加到 Todo 列表并更新状态,随后系统跳转回上一页面显示更新后的任务列表。
首先,定义了一个
add()
函数,它接收一个 data 对象 inputValue,其中包含输入框中的文本。代码使用 concat()
方法将当前 Todo 列表和新的 Todo 项一起添加到 app.todos 状态中。最后系统跳转回上一页面,以便继续添加新任务。
add() { app.todos.push({ text: this.data.inputValue, compeleted: false, }); tt.setStorage({ key: "todos", data: app.todos, }); // 添加完毕,返回上一页 tt.navigateBack(); },
预览与调试
开发者可直接使用 IDE 进行预览,或使用沙盒环境进行调试。
预览
你可以在 IDE 的模拟器中预览小程序。
也可以在 IDE 顶部的工具栏中点击「预览」,然后选择「推送预览」或「扫码预览」。
调试
为了便于开发者在真实移动设备上调试小程序,IDE 提供了真机调试功能。
总结
你已经完成了 Todo List 小程序开发。
你在该 Codelab 中了解了:
- •如何创建小程序项目
- •如何完成全局配置
- •如何开发页面
- •如何预览与调试