本节以 Todo App 模板小程序为例,介绍商家应用的文件结构,以及每种文件类型在小程序中的作用。Todo App 是一个简单的待办事项管理小程序,实现了用户登录、新增自定义待办事项、划除或恢复待办事项的功能。
app.json 是商家应用的全局配置,在这个文件中可以配置应用的页面列表、默认窗口标题、导航栏背景色等。更多配置参见 文档配置。
{ "pages": [ "pages/todos/todos", "pages/add-todo/add-todo" ], "window": { "defaultTitle": "Todo App", "titleBarColor": "#323239" } }
app.acss 定义了全局样式,作用于当前应用的所有页面。
page { flex: 1; display: flex; background: #323239; font-family: "pingFang SC" "pingFang"; }
上例中的 page 为框架支持的特殊选择器,会匹配框架提供的页面根节点容器。
app.js 用于注册应用,在这个文件中可以配置应用的生命周期,声明全局数据,调用丰富的 API,如以下获取用户授权及获取用户信息 API 等,更多 API 参见 API 文档。
App({ // 声明全局数据 todos:[ { text: 'Learning Javascript', completed: true }, { text: 'Learning ES2016', completed: true }, { text: 'Learning 商家应用', completed: false } ], userInfo: null, // 声明全局方法 getUserInfo() { return new Promise((resolve, reject) => { if (this.userInfo) resolve(this.userInfo); // 调用用户授权 API my.authorize({ scopes:'scope.userInfo', success: authcode => { console.info(authcode); // 调用获取用户信息 API my.getAuthUserInfo({ success: res => { this.userInfo = res; resolve(this.userInfo); }, fail: () => { reject({}); }, }); }, fail: () => { reject({}); }, }); }); }, });
可以看到,全局的逻辑代码放在 App({})中,声明了全局数据 todos 、 userInfo ,以及全局方法 getUserInfo()。
todos 全局数据中已经存储了一些数据,即 Todo App 小程序中已有的一些待办事项。
全局方法 getUserInfo() 调用了授权 API my.authorize,以及获取用户信息 API my.getAuthUserInfo ,并将获取到的用户信息存储在 userInfo 中。
在这个示例中,我们有两个页面,Todo List 页面和 Add Todo 页面,他们都在 pages 目录下。小程序的所有页面路径必须写在 app.json 中,路径从项目根目录开始且不能包括后缀名,pages 的第一个页面就是小程序的首页。
每一个 页面 由同路径下的四种类型文件组成,即 .json 后缀的配置文件,.axml 后缀的模版文件,.acss 后缀的样式文件,.js 后缀的逻辑脚本文件。
todos.json 用于配置当前页面的窗口表现。这里定义了使用一个自定义组件 add-button ,指定它的组件名称及对应的路径。自定义组件的具体使用后面会讲述。
{ "usingComponents": { "add-button": "/components/add-button/add-button" } }
页面配置文件不是必须的。当存在页面配置文件时,各个页面配置项会优先于 app.json 中 window 的同名配置项。当不存在页面配置文件,则直接使用 app.json 中的默认配置。因此,Todo List 页面的标题为 app.json 中指定的 defaultTitle ,即?Todo App
。
todos.axml 为页面结构模版文件。
<view class="page-todos"> <view class="user"> <image class="avatar" src="{{user.avatar || '../../assets/logo.png'}}" background-size="cover"></image> <view class="nickname">{{user.nickName && user.nickName + '\'s' || 'My'}} Todo List</view> </view> <view class="todo-items"> <checkbox-group class="todo-items-group" onChange="onTodoChanged"> <label a:for="{{todos}}" a:for-item="item" class="todo-item {{item.completed ? 'checked' : ''}}" a:key="*this"> <checkbox class="todo-item-checkbox" value="{{item.text}}" checked="{{item.completed}}" /> <text class="todo-item-text">{{item.text}}</text> </label> </checkbox-group> </view> <view class="todo-footer"> <add-button text="Add Todo" onClickMe="addTodo" ></add-button> </view> </view>
使用 <view/>
、<image/>
、<text/>
、<button/>
、<label/>
、<checkbox/>
,来搭建页面结构以及通过 Mustache 语法两对大括号({{}})绑定 todos 数据。
绑定数据可以查阅此文档
绑定事件可以查阅此文档
todos.js 是页面的逻辑脚本文件,小程序页面的逻辑代码必需包含在 Page({}) 中。
// 获取全局 app 实例 const app = getApp(); Page({ // 声明页面数据 data: {} // 监听生命周期回调 onLoad onLoad() { // 获取用户信息并存储数据 app.getUserInfo().then( user => this.setData({ user, }), ); }, // 监听生命周期回调 onShow onShow() { // 设置全局数据到当前页面数据 this.setData({ todos: app.todos }); }, // 事件处理函数 onTodoChanged(e) { // 修改全局数据 const checkedTodos = e.detail.value; app.setTodos(app.todos.map(todo => ({ ...todo, completed: checkedTodos.indexOf(todo.text) > -1, }))); this.setData({ todos: app.todos }); }, addTodo() { // 进行页面跳转 my.navigateTo({ url: '../add-todo/add-todo' }); }, });
在这个文件中可以:
监听并处理页面的生命周期函数 onShow
onLoad;
获取小程序实例以及其他页面实例 getApp
getCurrentPages;
声明并处理数据 data;
响应页面交互事件,调用 API 等;
这里需要注意的是 app.todos
是来自 app.js 中全局的变量定义。
todos.acss 定义页面局部样式。指定 todos.axml 中不同元素的样式,包括位置、背景颜色、字体、字体颜色等。语法可以查阅 样式 文档。
页面的 acss 文件不是必须的,但对于相同选择器,页面局部样式会覆盖 app.acss 全局样式。
.page-todos { display: flex; flex-direction: column; width: 100%; max-height: 100vh; } .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; } .nickname { padding-top: 40rpx; text-align: center; font-size: 40rpx; font-weight: 100; } .todo-items { flex-grow: 1; font-size: 34rpx; padding: 50rpx 120rpx; color: #0EFFD6; overflow: auto; } .todo-items-group { display: flex; flex-direction: column; } .todo-item { position: relative; margin-bottom: 50rpx; padding-left:80rpx; line-height: 70rpx; height: 80rpx; box-sizing: border-box; border: 2px solid rgb(14, 255, 214); border-radius: 100rpx; overflow: hidden; text-overflow: ellipsis; /* white-space:nowrap; */ transition: border 0.2s; } .todo-item:last-child { margin-bottom: 0; } .todo-item::before { content: ''; position: absolute; left: 12rpx; margin-right: 20rpx; width: 45rpx; height: 45rpx; background-color: rgba(14, 222, 255, 0.3); border-radius: 50%; top: 50%; transform: translateY(-50%); transition: background-color 0.2s; } .todo-item::after { content: ''; position: absolute; left: 29rpx; width: 8rpx; height: 18rpx; top: 50%; transform: translateY(-60%) rotate(38deg); border: 4rpx solid #FFF; border-width: 0 4rpx 4rpx 0; opacity: 0; transition: opacity 0.2s; } .todo-item-checkbox { display: none; } .checked .todo-item-text { text-decoration: line-through; color: #1AA0B8; } .checked.todo-item { border: 2px solid rgba(14, 222, 255, 0.2); } .checked.todo-item::before { background-color: rgba(14, 222, 255, 0.2); } .checked.todo-item::after { opacity: 1; } .todo-footer { flex-shrink: 0; padding: 50rpx 0 100rpx; font-size: 48rpx; font-weight: 200; text-align: center; }
add-todo.json 声明自定义组件名称和路径。
{ "usingComponents": { "add-button": "/components/add-button/add-button" } }
add-todo.axml 为页面结构模版文件。
<view class="page-add-todo"> <view class="add-todo"> <input class="add-todo-input" placeholder="What needs to be done?" onBlur="onBlur" value="{{inputValue}}" /> </view> <view class="todo-footer"> <add-button text="Add Todo" onClickMe="add" ></add-button> </view> </view>
在这个页面中有两个核心知识点:
① 使用<input/>
组件来接受用户输入。
② <add-button>
是一个自定义组件,我们可以将一些功能完整的代码封装为自定义组件,便于在其他地方复用。
add-todo.js 页面逻辑代码。
const app = getApp(); Page({ data: { inputValue: '', }, onBlur(e) { this.setData({ inputValue: e.detail.value, }); }, add() { app.todos = app.todos.concat([ { text: this.data.inputValue, compeleted: false, }, ]); my.navigateBack(); }, });
add-todo.acss 同 todos.acss?用法一致,这里不再赘述。