案例运用的知识:
为了有更好的体验,更简单的规范和管理,微信首先推出了小程序的概念,小程序是寄生在微信上的,微信对其做了一些限制,但是也扩展了其能力,CSS3 和 HTML5 基本上全部支持。
小程序是一种无需下载安装即可使用的应用,它实现了应用“触手可及”的梦想;用户扫一扫或者搜一下即可打开应用,不想使用直接关闭,无需卸载。真正实现了“用完即走”的理念,不用关心安装太多应用的问题。
小程序的技术栈是高仿web浏览器,没有什么新技术,就是讲web标准本土化一下,前端基本上可以无缝切入。说下我的小程序官方文档的读后感,简易教程不到一小时就能读完,然后写出相应的 hello world 的基础实例。这感觉 so easy 嘛,再去看框架,组件和api,从网上搜各种小程序进阶编程,你就会发现简易教程只不过是小程序这本书的序,浏览全部这本书,有个印象大概需要一个月,所以小程序的水也是很深。当然,我们这里探探水,更多精彩需要你们到公司结合实际业务自己领略。
内容、工具类小程序
知乎、微博、摩拜单车、腾讯地图
零售类小程序
拼多多、京东购物、小米有品、每日优鲜
游戏、娱乐类小程序
跳一跳、欢乐斗地主、斗鱼直播、YY直播
注册小程序开发账号
激活邮箱(注意:一个邮箱只能绑定一个微信公众平台或者开放平台,不能是个人微信号绑定的邮箱)
信息登记
登录小程序管理后台
绑定开发者(多人协同开发)
小程序的版本
预览 === 开发版本 —> 上传 ===> 体验版本 —> 审核 ===> 线上版本
pages
微信小程序所有的页面每一个页面包括 js 页面逻辑 、 json 单个页面的配置 、wxml 页面内容 、wxss 样式
pages
文件夹里是小程序的各个页面,每个界面一般都由.wxml
、.wxss
、.js
、.json
四个文件组成,四个文件必须是相同的名字和路径
.js
是页面的脚本代码,通过Page()
函数注册页面。可以指定页面的初始数据、生命周期、事件处理等
.wxml
是页面的布局文件,只能使用微信定义的组件
.wxss
是样式表
.json
页面配置文件,设置与
utils
工具类,复用的 js 代码模块
js 模块通过module.exports
对外暴露接口,其它使用的地方通过 var utils = require('../../utils/util.js')
进行引用
app.js 监听并处理小程序的生命周期、声明全局变量
页面的.js
文 件可以通过var app = getApp()
获取其实例,调用其中定义的方法和变量,但不可以调用生命周期的方法
app.json 小程序全局配置
// app.json 配置介绍
"pages": [ // 配置小程序的组成页面,第一个代表小程序的初始页面
"pages/index/index", //不需要写index.wxml,index.js,index,wxss,框架会自动寻找并整合
"pages/logs/logs"
],
"window": { // 设置小程序的状态栏、标题栏、导航条、窗口背景颜色
"navigationBarBackgroundColor": "#ffffff", //顶部导航栏背景色
"navigationBarTextStyle": "black", //顶部导航文字的颜色 black/white
"navigationBarTitleText": "微信接口功能演示", //顶部导航的显示文字
"backgroundColor": "#eeeeee", //窗口的背景色
"backgroundTextStyle": "light", //下拉背景字体、loading 图的样式,仅支持 dark/light
"enablePullDownRefresh": "false", //是否支持下拉刷新 ,不支持的话就直接不写!
"disableScroll": true, // 设置true不能上下滚动,true/false,注意!只能在 page.json 中有效,无法在 app.json 中设置该项。
},
"tabBar": { //底部tab或者顶部tab的表现,是个数组,最少配置2个,最多5个
//只有定义为 tabBar 的页面,底部才有 tabBar
"list": [{ //设置tab的属性,最少2个,最多5个
"pagePath": "pages/index/index", //点击底部 tab 跳转的路径
"text": "首页", //tab 按钮上的文字
"iconPath": "../img/a.png", //tab图片的路径,本地路径
"selectedIconPath": "../img/a.png" //tab 在当前页,也就是选中状态的路径
}, {
"pagePath": "pages/logs/logs",
"text": "日志"
}],
"color": "red", //tab 的字体颜色
"selectedColor": "#673ab7", //当前页 tab 的颜色,也就是选中页的
"backgroundColor": "#2196f3", //tab 的背景色
"borderStyle": "white", //边框的颜色 black/white
"position": "bottom" //tab处于窗口的位置 top/bottom
},
"networkTimeout": { //默认都是 60000 秒一分钟
"request": 10000, //请求网络超时时间 10000 秒
"downloadFile": 10000, //链接服务器超时时间 10000 秒
"uploadFile": "10000", //上传图片 10000 秒
"downloadFile": "10000" //下载图片超时时间 10000 秒
},
"debug": true //项目上线后,建议关闭此项,或者不写此项
app.wxss
小程序全局样式(引入外部样式的时候只在这里面引入即可)
project.config.json
微信开发者工具的配置信息
siteMapLocation 详情参见此处 一般不用更改,在此不做说明
注意: 定义在app.wxss
中的全局样式,作用于每个页面。定义在page
的.wxss
文件只作用于对应的页面,会覆盖app.wxss
中相同的选择器
传统的视图和数据绑定
小程序中视图和数据绑定 对象数据变化直接影响视图,视图变化必须同过事件驱动才能改变对象数据
WXML中的动态数据均来自对应的
Page
的data
。数据绑定使用 Mustache 语法(双大括号将变量包起来)。
基本使用
{{ message }}
Page({
data: {
message: 'Hello MINA!',
name: "李白",
status: 3
}
})
绑定属性
支持表达式
是否显示
{{'hello' + name}}
更多使用方式 查看
{{index}}: {{item.message}}
Page({
data: {
array: [
{
message: 'foo',
},
{
message: 'bar'
}]
}
})
使用
wx:for-item
可以指定数组当前元素的变量名,使用wx:for-index
可以指定数组当前下标的变量名:
{{idx}}: {{itemName.message}}
True
也可以用
wx:elif
和wx:else
来添加一个 else 块:
已登录
请登录
A
B
C
block
的使用
并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性。
{{item}}
看flag是否为true
ID/class
ID名/类名
style
行内样式
hidden
组件是否显示
data-*
自定义属性
可以通过事件的e.target.dataset.*
来获取设置的自定义属性
// js
Page({
clickMe: function(event) {
var testId = event.target.dataset.testid;
}
})
注意:不要直接写 hidden=“false” , 其计算结果是一个字符串,转成 boolean 类型后代表真值。现在好像可以不带花括号,建议加上
wx:if
和hidden
都可以实现组件的显示和隐藏效果;区分场景:如果需要频繁切换,用 hidden
更好;如果执行条件不大可能改变则使用 wx:if
较好
参见官方API
rpx
可以根据屏幕宽度进行自适应。小程序规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。
[外链图片转存失败(img-ja1Z9CAz-1569393956076)(https://upload-images.jianshu.io/upload_images/6784887-cc603a9c54cc1dbb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
使用@import
语句可以导入外联样式表,@import
后跟需要导入的外联样式表的相对路径,用;
表示语句结束。
/** common.wxss **/
.small-p {
padding:5px;
}
/** app.wxss **/
@import "common.wxss";
.middle-p {
padding:15px;
}
wxss 选择器的类型和优先级与 css 中的相同
小程序的运行机制
//app.js 小程序的生命周期函数在 app.js 中声明
App({
onLaunch: function() {
//小程序初始化(全局只触发一次)
},
onShow: function() {
//小程序显示
},
onHide: function() {
//小程序隐藏
},
onError: function(msg) {
//小程序错误
},
})
用户首次打开小程序,触发 onLaunch
(全局只触发一次)。
小程序初始化完成后,触发 onShow
方法,监听小程序显示。
小程序从前台进入后台,触发 onHide
方法。
小程序从后台进入前台显示,触发 onShow
方法。
小程序后台运行一定时间,会被销毁。
小程序出错,触发onError
前台、后台定义: 当用户点击右上角关闭,或者按了设备返回键离开微信,小程序并没有直接销毁,而是进入了后台;当再次进入微信或再次打开小程序,又会从后台进入前台
页面栈以栈(先进后出)的形式维护页面与页面之间的关系,路由更改了页面栈的层数,小程序页面栈最多10层
小程序提供了
getCurrentPages()
函数获取页面栈,第一个元素为首页,最后一个元素为当前页面
wx.switchTab
跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面,页面栈内仅剩当前页。
wx.switchTab({
url: '/pages/index/index'
})
wx.reLaunch
关闭所有页面,打开到应用内的某个页面,页面栈内仅剩当前页面。
// index Tab页面 --> A页面 --> B页面 --> C页面 当前 C 页面
// 显示 A 页面
wx.reLaunch({
url: '../A/A'
})
// 显示index Tab页面
wx.reLauch({
url: "/pages/index/index"
})
wx.redirecTo
关闭当前页面,跳转到应用内的某个页面,页面栈数量不增不减。但是不允许跳转到 tabbar 页面。
// index Tab页面 --> A页面 --> B页面 --> C页面 当前 C 页面
// 重定向到 A页面,此时页面栈 [index页面,A页面,B页面,A页面]
wx.redirecTo({
url: '../A/A'
})
wx.navigateTo
保留当前页面,跳转到应用内的某个页面,页面栈数量 + 1。但是不能跳到 tabbar 页面。
// 当前 A页面, 跳转到 B页面
wx.navigateTo({
url: '../B/B'
})
wx.navigateBack
关闭当前页面,返回上一页面或多级页面 页面栈数量 - n。可通过 getCurrentPages 获取当前的页面栈,决定需要返回几层。
// A页面 --> B页面 --> C页面 当前 C 页面
wx.navigateBack(); // 返回 B 页面
// 返回A页面
wx.navigateBack({
delta: 2 // 返回的页面数,如果大于现有页面数,则返回首页
})
小程序页面内组件跳转
跳转
onLoad
方法。onShow
方法,显示页面。onReady
方法,渲染页面元素和样式onHide
方法。onUnload
方法事件是视图层到逻辑层的通讯方式,可以将用户的行为反馈到逻辑层进行处理。事件可以绑定在组件上,触发事件后,就会执行逻辑层中对应的事件处理函数。小程序的事件对象可以查看自定义属性的值
小程序通过 bind*/catch*
(*代表事件类型) 给组件添加事件;bind
方式绑定的事件会向上冒泡,catch
方式绑定的事件不会向上冒泡
outer view
middle view
inner view
常见事件类型:toushstart touchmove touchcancel touchend tap longpress longtap transitionend animationstart animationiteration animationend touchforcechange
小程序表单、swiper等组件有自己的事件类型,详情参见官方API各组件详细内容事件介绍
通过事件改变页面 data
对象内容的方式
page({
data{
count: 0
},
changeCount: function (){
this.setData({
count: this.data.count ++
})
}
})
需要在捕获阶段监听事件时,可以采用capture-bind
、capture-catch
关键字,后者将中断捕获阶段和取消冒泡阶段
在下面的代码中,点击 inner view 会先后调用handleTap2
、handleTap4
、handleTap3
、handleTap1
outer view
inner view
如果将上面代码中的第一个capture-bind
改为capture-catch
,将只触发handleTap2
outer view
inner view
作用:提取页面公共结构,达成某结构复用,精简代码
建立 components 文件夹
在文件夹下建立自定义名称的文件夹,在其内建立 component 相应文件
在要引入的页面的json配置中:
"usingComponents": {
// 自定义标签名: 自定义组件的路径(json文件中不能有此注释)
"component-tag-name": "path/to/the/custom/component"
}
在要引入的页面书写
如果想要在页面引入的组件内添加其它组件,可以使用slot插槽
这里是组件的内部节点
这里是插入到组件slot中的内容
默认情况下,一个组件的wxml中只能有一个slot。需要使用多slot时,可以在组件js中声明启用
Component({
options: {
multipleSlots: true // 在组件定义时的选项中启用多slot支持
},
properties: { /* ... */ },
methods: { /* ... */ }
})
这里是组件的内部细节
这里是插入到组件slot name="before"中的内容
这里是插入到组件slot name="after"中的内容
组件的事件传播
Component({
properties: {},
methods: {
onTap: function(){
var myEventDetail = {} // detail对象,提供参数
// 触发事件的选项
var myEventOption = {
bubbles: true/false, // 事件是否冒泡
composed: true/false, // 事件是否可以穿越组件边界,为false时,事件将只能在引用组件的节点树上触发,不进入其他任何组件内部
capturePhase: true/false // 事件是否拥有捕获阶段
}
this.triggerEvent('myevent', myEventDetail, myEventOption)
}
}
})
Page({
onMyEvent: function(e){
e.detail // 自定义组件触发事件时提供的detail对象
}
})
注意:
组件对应 wxss
文件的样式,只对组件wxml内的节点生效。编写组件样式时,需要注意以下几点:
组件内部不能使用id选择器、属性选择器、标签选择器
全局样式和引用组件的页面的样式对组件无效,组件只能继承引用页面的 font
、color
样式
在引用组件的页面尽量避免使用后代选择器更改组件内的样式
在组件的 js文件 中声明:
Component({
options: {
// styleIsolation 选项从基础库版本 2.6.5 开始支持。它支持以下取值:isolated 表示启用样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响(一般情况下的默认值);apply-shared 表示页面 wxss 样式将影响到自定义组件,但自定义组件 wxss 中指定的样式不会影响页面;shared 表示页面 wxss 样式将影响到自定义组件,自定义组件 wxss 中指定的样式也会影响页面和其他设置了 apply-shared 或 shared 的自定义组件。(这个选项在插件中不可用。)
styleIsolation: 'isolated'
}
})
调用 wx.chooseImage
方法,详细方法参见微信官方API
/* 选择图片 */
chooseImage(){
var that = this;
wx.chooseImage({
count: 6,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success(res) {
// tempFilePath可以作为img标签的src属性显示图片
const tempFilePaths = res.tempFilePaths
// console.log(tempFilePaths);
that.setData({
imgsLs: tempFilePaths
})
}
})
},
调用 wx.previewImage
方法,详细方法参见微信官方API
/* 预览图片方法 */
lookImage: function(){
wx.previewImage({
current: '', // 当前显示图片的http链接
urls: [] // 需要预览的图片http链接列表
})
}
调用 wx.uploadFile
方法,详细方法参见微信官方API
/** 上传图片方法 */
submit: function(){
const that = this;
// 上传图片
const progress = wx.uploadFile({
url: 'https://sm.ms/api/upload',
filePath: that.data.imgSrc,
name: "smfile",
success(res){
console.log(res);
const url = JSON.parse(res.data).data.url;
}
})
// 图片上传进度
progress.onProgressUpdate((res) => {
console.log('上传进度', res.progress)
that.setData({
progress: res.progress
})
console.log('已经上传的数据长度', res.totalBytesSent)
console.log('预期需要上传的数据总长度', res.totalBytesExpectedToSend)
})
},
使用 wx.request
方法,详情参见官方API
// 示例: 请求豆瓣最新上映电影列表
wx.request({
url: 'http://www.bufantec.com/api/douban/movie/in_theaters',
data: {
start: "1",
limit: "20"
},
header: {
'content-type': 'application/json' // 默认值
},
success(res) {
console.log(res.data)
}
})
wx.request
返回数据说明: 第一个data 是微信返回的信息,data 里面的 data 才是后台返回给我们的数据
小程序销毁会删除本地存储的信息。从后台返回进入小程序,信息不会被删除
// 异步存值
wx.setStorage({
key:key,
data:value
})
// 同步存值
wx.setStorageSync(key,value)
// 同步获取
wx.getStorageSync(key)
// 异步取值
wx.getStorage({
key: "key",
success: res => {
console.log(res.data)
}
})
// 异步删除
wx.removeStorage({
key: 'key',
success (res) {
console.log(res)
}
})
// 同步删除
wx.removeStorageSync('key')
app.globalData
// 在app.js中设置对象 globalData{}
// 在要传值的页面引入 var app = getApp();
app.globalData.name = "王安石";
// 在要接受值的页面引入 var app = getApp();
console.log(app.globalData.name);
wx.setStorageSync
// 在要传值的页面
wx.setStorageSync("name","陆游");
// 在要接受值的页面
wx.getStorageSync("name");
// 在要跳转的路由后面加上 ?key=value&key1=value1 的形式;注意: wx.switchTab 中的 url 不能传参数。
// 要跳转的页面可以通过onload函数的形参options接受上一个页面传递的值
wx.navigateTo({
url:'../pageD/pageD?name="李白"'
})
// B页面-接收数据 通过onLoad的option...
Page({
onLoad: function(option){
console.log(option.name)
}
})
用户打开小程序 ==> 查看本地是否有用户信息
==> 有,直接使用
==> 没有 ===> 调用查看是否授权
==> 未授权 ===> 显示
button
,调用授权弹窗(获取用户信息,存储到本地) ==> 用户离开小程序后再次进入 ==> 从本地获取用户信息 ==> 已授权 ===> 不显示
button
,调用wx.getUserInfo
(获取用户信息,存储到本地) ==> 用户离开小程序后再次进入 ==> 从本地获取用户信息
onload: function (){
var that = this;
var userInfo = wx.getStorageSync("userInfo");
if(userInfo){
this.setData({
userInfo: userInfo
})
}else{
// 查看是否授权
wx.getSetting({
success(res) {
// 如果已经授权,查看是否是获取用户信息授权
if (res.authSetting['scope.userInfo']) {
// 已经授权,可以直接调用 getUserInfo 获取头像/昵称/性别/地址
wx.getUserInfo({
success: function (res) {
// 设值
that.setData({
userInfo: res.userInfo
})
// 存储到本地
wx.setStorageSync("userInfo", res.userInfo)
}
})
}else{
// 未授权,显示button授权按钮
that.setData({
isShow: true
})
}
}
})
}
}
逻辑: 用户打开小程序 == > 查看本地是否有openId(有,直接使用) 没有 ==> 调用登陆接口,调用后台接口,拿取openId
,存储到本地
// 微信登陆
var uid = wx.getStorageSync("uid");
if(!uid){
wx.login({
success(res) {
// 成功返回 code 用户登录凭证(有效期五分钟)
if (res.code) {
// 将 code 码发送给后台
wx.request({
url: 'http://59.110.138.169/admin/xy/lite/student/onLogin',
data: {
code: res.code
},
success: function (res){
console.log(res);
wx.setStorageSync("uid",'res.data.data.id');
}
})
} else {
console.log('登录失败!' + res.errMsg)
}
}
})
}
wx.showLoading 和 wx.showToast 同时只能显示一个
wx.showToast({
title: '成功', // 提示的内容
icon: 'success', // 图标 值: success/loading/none
// image: 自定义图标的本地路径,image 的优先级高于 icon
duration: 2000 // 提示的延迟时间,默认1500ms
// mask // 是否显示透明蒙层,防止触摸穿透 默认 false
// success 成功回调 fail 失败回调 complete 接口调用完成回调
})
wx.hideToast() // 可以关闭页面的提示框
**注意:**当提示框显示图标时,toast
最多显示7个汉字长度,当图标为 none
时,最多显示两行。
wx.showModal({
title: '提示', // 提示的标题
content: '这是一个模态弹窗', // 提示的内容
// showCancel 是否显示取消按钮
// cancelText 取消按钮的文字,最多 4 个字符
// cancelColor 取消按钮的文字颜色,必须是 16 进制格式的颜色字符串
// confirmText 确认按钮的文字,最多 4 个字符
// confirmColor 确认按钮的文字颜色,必须是 16 进制格式的颜色字符串
success (res) { // 成功后的回调
if (res.confirm) { // confirm为true表示用户点击了确定按钮
console.log('用户点击确定')
} else if (res.cancel) { // cancel为true表示用户点击了取消(用于 Android 系统区分点击蒙层关闭还是点击取消按钮关闭)
console.log('用户点击取消')
}
}
})
注意
显示 loading
提示框。需主动调用 wx.hideLoading
才能关闭提示框
wx.showLoading({
title: '加载中', // 提示的内容
mask: false, // 是否显示透明蒙层,防止触摸穿透,默认为false
// success 成功回调 fail 失败回调 complete 接口调用完成回调
})
wx.showActionSheet({
itemList: ['A', 'B', 'C'], // 按钮的文字数组,数组长度最大为 6
itemColor: '#333333', // 按钮的文字颜色,默认#000000
success (res) { // 成功后的回调
console.log(res.tapIndex) // tapIndex 用户点击的按钮序号,从上到下的顺序,从0开始
},
fail (res) { // 失败的回调函数
console.log(res.errMsg)
}
// complete
})
注意
// 页面 ...
swiper.square-dot .wx-swiper-dot {
background-color: #000;
opacity: 0.4;
width: 10rpx;
height: 10rpx;
border-radius: 20rpx;
margin: 0 8rpx !important;
}
swiper.square-dot .wx-swiper-dot.wx-swiper-dot-active{
opacity: 1;
width: 30rpx;
}
// 获取页面栈,数组第一个是首页,最后一个是当前页
var pages = getCurrentPages();
// 当前页面
var currPage = pages[pages.length - 1];
// 当前页面设置值
currPage.setData({
wait: true
})
// 前一个页面
var prevPage = pages[pages.length -2];
// 前一个页面设置值
prevPage.setData({
wait: true
})
/* 组件添加向右的箭头的样式 */
.arrow {
position: relative;
}
.arrow:after {
position: absolute;
top: 50%;
right: 28rpx;
margin-top: -8rpx;
display: block;
content: " ";
height: 18rpx;
width: 18rpx;
border-width: 3rpx 3rpx 0 0;
border-color: #888888;
border-style: solid;
transform: matrix(0.71, 0.71, -0.71, 0.71, 0, 0);
}
/* 扩散元素自身的颜色的阴影写法 */
.shadow-blur {
position: relative;
}
.shadow-blur::before {
content: "";
display: block;
background: inherit;
filter: blur(10rpx);
position: absolute;
width: 100%;
height: 100%;
top: 10rpx;
left: 10rpx;
z-index: -1;
opacity: 0.4;
transform-origin: 0 0;
border-radius: inherit;
transform: scale(1, 1);
}
/* 两端翘起的阴影效果 */
.shadow-warp {
position: relative;
background-color: #fff;
box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.1);
}
.shadow-warp:before,
.shadow-warp:after {
position: absolute;
content: "";
top: 20rpx;
bottom: 30rpx;
left: 20rpx;
width: 50%;
box-shadow: 0 30rpx 20rpx rgba(0, 0, 0, 0.2);
transform: rotate(-3deg);
z-index: -1;
}
.shadow-warp:after {
right: 20rpx;
left: auto;
transform: rotate(3deg);
}
/* button的边框样式是通过::after方式实现的,如果在button上定义边框就会出现两条边框线,所以我们可以使用::after的方式去覆盖默认值 */
button::after {
border: none;
}
app.json
中进行注册,否则不能访问app.json
中pages
数组的第一项代表小程序的初始页面,小程序中新增/减少页面,都需要对 pages
数组进行修改this.data
无法改变页面的状态,还会造成数据不一致tabBar
上面的按钮 iconPath
不支持网络路径,icon
大小限制为40kb
,官方推荐尺寸是 81* 81setStorage
本地缓存最大为10MB1MB
,否则代码包将上传失败JsCore
中运行,JsCore是一个没有窗口对象的环境,所以不能在脚本中使用window
,也无法在脚本中操作组件