开发 Todo List 小程序
收藏
我的收藏

简介​

抖音小程序是可以在抖音、抖音极速版、抖音火山版、今日头条、今日头条极速版 App 中运行的小程序。一套代码,可以支持 Android、iOS 双端适配。​
本教程将带你从零开始创建一个 Todo 小程序,你将快速了解如何调用小程序组件、小程序 API、以及如何让小程序适配移动端。​
该小程序将实现以下功能:​
    用户登录并获取用户的基本信息。​
    用户可以添加和更新自己的 Todo。​

开发路径说明​

开发并发布小程序的完整路径如下:​
    4.全局配置。​
    5.开发页面。​
    6.预览与调试。​
    7.发布小程序。​
其中,入驻开发者平台过程比较复杂,而且入驻开发者平台和创建小程序还需要审核,所以为了方便您快速体验开发过程,建议:​
    1.使用测试号创建小程序项目。​
    2.全局配置。​
    3.开发页面。​
    4.预览与调试。​
    5.待入驻开发者平台、创建小程序后,再发布小程序。​

预览效果​

使用抖音、抖音极速版、今日头条、今日头条极速版 App 扫描以下代码,体验 Todo 小程序 Demo。​

可运行的示例代码​

可以在抖音开发者工具 IDE 中导入代码片段 https://developer.open-douyin.com/ide/minicode/iCJaQSy,也可以选择跟随教程一步步实现预期的功能。

视频教程​

创建小程序项目​

安装 IDE​

根据自己的操作系统,下载并安装抖音开发者工具(IDE) 。​
IDE 支持小程序开发、调试、预览、上传等基本功能,能够帮助你高效的开发小程序。关于 IDE 的具体介绍可以参考开发者工具概述。​

在 IDE 中新建小程序项目​

    1.打开 IDE,用邮箱、手机、抖音扫码都可以完成登录。​
    2.在 IDE 左侧导航栏点击「小程序」,在「小程序」界面点击「新建」。​
    3.在「新建项目」页面填写小程序的「项目目录」、「项目名称」、「AppID」,选择「开发语言」和「项目模板」,然后点击「新建」。​
    AppID:​
    在开发者平台创建的或被邀请协作的小程序的 AppID。​
    使用沙盒小程序的 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 个文件组成:​
文件类型
必需
作用
.js
是​
页面逻辑​
是​
页面结构​
否​
页面配置​
否​
页面样式表​
注意
为了方便开发者减少配置项,描述页面的 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.jsonproject.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 的方式来表达数据。JSON 的 key 必须包裹在一个双引号中,在实践中,编写 JSON 的时候,忘了给 key 值加双引号或者是把双引号写成单引号是常见错误。​
JSON 的值只能是以下几种数据格式,其他任何格式都会触发报错,例如 JavaScript 中的 undefined。​
    数字,包含浮点数和整数​
    字符串,需要包裹在双引号中​
    Bool 值,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 由标签、属性等构成。但是也有很多不一样的地方,具体差异如下:​
    1.标签名字不一样。​
    往往写 HTML 的时候,经常会用到的标签是 divpspan,开发者在写一个页面的时候可以根据这些基础的标签组合出不一样的组件,例如日历、弹窗等等。换个思路,既然大家都需要使用这些组件,为什么我们不能把这些常用的组件包装起来,来大大提高我们的开发效率呢?​
    从上边的例子可以看到,小程序的 TTML 用的标签是 viewbuttontext 等等,这些标签就是小程序给开发者包装好的基本能力,我们还提供了地图、视频、音频等组件能力。​
    更多详细的组件介绍请参考小程序组件列表。​
    2.多了一些 tt:if 这样的属性以及 {{ }} 这样的表达式。​
    在网页的一般开发流程中,我们通常会通过 JS 操作 DOM (对应 HTML 的描述产生的树),以引起界面的一些变化响应用户的行为。例如,用户点击某个按钮的时候,JS 会记录一些状态到 JS 变量里边,同时通过 DOM API 操控 DOM 的属性或者行为,进而引起界面一些变化。当项目越来越大的时候,你的代码会充斥着非常多的界面交互逻辑和程序的各种状态变量,显然这不是一个很好的开发模式,因此就有了 MVVM 的开发模式(例如 React, Vue),提倡把渲染和逻辑分离。简单来说就是不要再让 JS 直接操控 DOM,JS 只需要管理状态即可,然后再通过一种模板语法来描述状态和界面结构的关系即可。​
    小程序的框架也是用到了这个思路,如果你需要把一个 "Hello World" 的字符串显示在界面上。​
    TTML 是这么写 :​
<text>{{ msg }}</text>
    JS 只需要管理状态即可:​
this.setData({ msg: "Hello World" });
    通过 {{ }} 的语法把一个变量绑定到界面上,我们称为数据绑定。仅仅通过数据绑定还不能完整地描述状态和界面的关系,还需要 if/else, for 等控制能力,在小程序里边,这些控制能力都用 tt: 开头的属性来表达。​

TTSS 样式​

TTSS 具有 CSS 大部分的特性,小程序在 TTSS 也做了一些扩充和修改。​
新增了尺寸单位。在写 CSS 样式时,开发者需要考虑到手机设备的屏幕会有不同的宽度和设备像素比,采用一些技巧来换算一些像素单位。TTSS 在底层支持新的尺寸单位 rpx ,开发者可以免去换算的烦恼,只要交给小程序底层来换算即可,由于换算采用浮点数运算,所以运算结果会和预期结果有一点点偏差。​
提供了全局样式和局部样式。和前边 app.json/page.json 的概念类似,你可以写一个 app.ttss 作为全局样式,会作用于当前小程序的所有页面,局部页面样式page.ttss 仅对当前页面生效。​
此外 TTSS 仅支持部分 CSS 选择器。​
更详细的文档可以参考 TTSS 。​

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" }) } })
响应用户的操作就是这么简单,更详细的事件可以参考文档 TTML-事件。​
此外你还可以在 JS 中调用小程序提供的丰富的 API,利用这些 API 可以很方便的调起小程序提供的能力,例如获取用户信息、本地存储、支付等。更多 API 可以参考文档小程序的 API 。​

全局配置​

在上一章中,你成功创建了你的第一个小程序项目,接下来将了解这个小程序的全局配置文件的含义和用法。​
小程序包含 4 个全局配置文件类型:​
    工程配置文件 project.config.json
    全局配置文件 app.json
    全局样式文件 app.ttss
    逻辑文件 app.js

工程配置文件 project.config.json​

project.config.json 是小程序项目的工程配置文件,包含小程序的 appidprojectname(项目名称)和 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​

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 的组件样式。​
TTSS 具有 CSS 大部分特性,同时也对 CSS 进行了扩充以及修改。详情请参见 TTSS。​
注意:
    具体的样式设计和类名取决于你的小程序的实际需求。
    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({ onLaunch() { try { const storage = tt.getStorageSync("todos"); if (Array.isArray(storage)) { this.todos = storage; } } catch (err) { console.log(err); } }, todos: [ { text: "学习 Javascript", completed: true, }, { text: "学习 ES2016", completed: true, }, { text: "学习 抖音小程序", completed: false, }, ], });

开发页面​

了解程序与页面​

小程序启动之后,在 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 实例,是全部页面共享的,把这个 App 实例称为程序。事实上,一个小程序是由多个“页面”组成的“程序”,每个页面承载不同的功能,页面之间可以互相跳转,之后所涉及的“页面”概念特指“小程序页面”。​

了解页面目录结构​

Todo Demo 小程序项目中包含两个页面,todos(更新 Todo 状态)和 add_todo(添加 Todo)。​
两个页面都位于 pages 目录下,且路径需要在 app.json 中声明。​
这两个页面中分别包含以下文件:​
todos 页面
add_todo 页面
说明
todos.js​
add_todo.js​
实现页面逻辑。用 JavaScript 语言编写,用于指定页面的初始数据、生命周期回调、事件处理函数等。详细用法参见页面。​
todos.json​
add_todo.json​
实现页面配置。​
页面的配置文件不是必须的。​
    如果没有,则直接使用 app.json 中的配置。​
    如果有,配置优先级高于 app.jsonwindow 下的同名配置项。​
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 class="todo-item-checkbox" value="{{item.text}}" checked="{{item.completed}}" /> <text class="todo-item-text">{{item.text}}</text> <view class="delete" catchtap="deleteItem" data-index="{{index}}" >×</view > </label> </checkbox-group> </view> <view class="todo-footer"> <button class="add-button" hover-class="none" bindtap="addTodo"> <text class="add-icon">+</text> </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"> </view>为用户信息,以下代码,实现了:​
    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 按钮组件实现。登录按钮的 class="login-btn",在todos.ttss 中通过.login-btn描述其样式。登录按钮的 bindtap="getUserProfile"bindtap 是所有组件绑定事件的方法,这里代表如果点击登录按钮,则会调用 tt.getUserProfile。​
    如果当前用户已登录,则展示 xxx 的记事本和用户头像。xxx 为实际用户名,通过嵌入变量 nickname 实现。用户头像通过 image 图片组件实现,src 为图片资源地址。​
条件判断和变量嵌入的具体写法可以参考 TTML-条件渲染TTML-数据绑定。​

实现 todos 页面的处理逻辑​

页面搭建完成后,我们就需要定义该页面的变量以及函数方法了。我们需要实现的效果是:​
    1.在 Todo 页面点击登录,同意获取个人信息后,完成登录,并自动获取用户的用户名和头像。​
    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, }); }, });
这段代码主要实现了一个待办事项列表的功能,包括切换任务完成状态、获取用户信息、添加新任务以及删除任务等。​
Page() 是小程序页面的代码。包括:​

数据声明​

data:{},用于声明页面的数据。​
data: {},

生命周期回调​

onLoad():监听页面加载时触发。​
onLoad() { },
onShow():这段代码是在一个页面的显示方法中的回调函数中执行的。其中:​
setData() 方法用于将全局数据设置到当前页面的数据中。在这个例子中,全局数据存储在 app.todos 中,并且将其设置为当前页面的数据。​
onShow() { // 设置全局数据到当前页面数据 this.setData({ todos: app.todos }); },

事件处理函数​

toggleState(e):处理用户点击事件,通过修改全局数据 app.todos 切换任务完成状态,并通过 tt.setStorage 将修改后的数据存储到本地。​
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, }); },
getUserProfile():通过tt.getUserProfile异步获取用户信息。会弹出授权提示窗,若用户同意,则会返回用户的真实数据,并存储到页面中。​
async getUserProfile() { // 获取用户信息并存储数据 try { const user = await app.getUserProfile(); this.setData({ user, }); } catch(err) { console.error(err); } },
进行页面跳转,跳转到 ../add-todo/add-todo 页面。​
addTodo() { // 进行页面跳转 tt.navigateTo({ url: "../add-todo/add-todo" }); },
通过修改全局数据 app.todos 删除某个任务,并将修改后的数据通过 tt.setStorage 存储到本地。​
deleteItem(e) { const { index } = e.currentTarget.dataset; app.todos.splice(index, 1); 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; }
使用<input> 组件接收用户输入,详情请参见 input 输入框。​

实现 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: '', },

生命周期回调​

该代码的作用是将当前文本框的值存储到数据中,以便后续使用。​
监听页面输入框的 onBlur 事件,在当文本框失去焦点时触发。其中,this 指代的是当前页面模块,setData() 方法用于设置数据,参数 inputValue 是指当前文本框的值。​
onBlur(e) { this.setData({ inputValue: e.detail.value, }); },

事件处理函数​

当用户点击「+ 添加」按钮时执行的。它将输入框中的文本添加到已有的 Todo 列表中,并将 Todo 列表存储在状态中。然后,它跳转回上一个页面。​
首先,定义了一个 add() 函数,它接收一个 data 对象 inputValue,其中包含输入框中的文本。代码使用 concat() 方法将当前 Todo 列表和新的 Todo 项一起添加到 app.todos 状态中。​
然后,代码调用 tt.setStorage 方法将 Todo 状态保存在钩子中并存储,以便在小程序的其他部分使用。​
最后,代码跳转回上一个页面,以便用户继续添加新的任务。​
add() { app.todos = app.todos.concat([ { text: this.data.inputValue, compeleted: false, }, ]); tt.setStorage({ key: 'todos', data: app.todos, }); tt.navigateBack(); },

预览与调试​

在小程序开发时,你可以直接使用 IDE 进行预览,使用 IDE 或沙盒环境进行调试。​
本文主要介绍如何使用 IDE 进行预览与调试,沙盒调试请参见沙盒使用指南。​

预览​

你可以在 IDE 的模拟器中预览小程序。​
也可以在 IDE 顶部的工具栏中点击「预览」,然后选择「推送预览」或「扫码预览」。​

调试​

你可以在 IDE 中通过调试器 + 模拟器调试小程序。详情请参见 IDE 调试。​
为了便于开发者在真实移动设备上调试小程序,IDE 中也提供了真机调试的功能。 ​
点击真机调试后会生成对应的二维码,可使用抖音 App 扫码,按照提示推动即可进行真机调试。详情请参见真机调试。​

总结​

恭喜,你已经完成了 Todo List 小程序开发。​
你在该 Codelab 中了解了:​
    如何创建小程序项目​
    如何完成全局配置​
    如何开发页面​
    如果预览与调试​
如果想了解更多 Codelabs,请返回 Codelabs。​