小程序是一种全新的连接用户与服务的方式,它可以在微信内被便捷地获取和传播,同时具有出色的使用体验
解释:小程序依赖于微信作为载体,呈现类似App的展示应用效果
小程序的主要开发语言是 JavaScript ,小程序的开发同普通的网页开发相比有很大的相似性。对于前端开发者而言,从网页开发迁移到小程序的开发成本并不高,但是二者还是多少有些许区别的,例如:
html
文件修改为wxml
文件css
文件修改为wxss
文件##
2011年1月,微信1.0发布
2012年3月,微信突破1亿用户
2013年8月,微信发布微信支付
2014年9月,企业号发布
2015年1月,微信第一条朋友圈广告
2016年1月,企业微信发布
2017年1月,小程序发布
开发者可使用微信客户端(6.7.2 及以上版本)扫码下方小程序码,体验小程序
开发小程序的第一步,你需要拥有一个小程序帐号,通过这个帐号你就可以管理你的小程序
进入小程序注册页根据指引填写信息和提交相应的资料,就可以拥有自己的小程序帐号
注册地址:微信小程序小程序是一种新的开放能力,可以在微信内被便捷地获取和传播,同时具有出色的使用体验。小程序开发者可在小程序内提供便捷、丰富的服务,如预订、商品购买、游戏、信息查询等。https://mp.weixin.qq.com/cgi-bin/wx?token=&lang=zh_CN
温馨提示
这里我们能选择的是个人和企业,如果是个人注册选择个人(我们这里选择个人),如果是企业注册选择企业
温馨提示
用户信息与微信注册用户信息保持一致
单独进入管理平台地址:微信公众平台
注意:目前对我们有用的是“开发管理 - 开发设置 - AppID”
小程序的 AppID 相当于小程序平台的一个身份证,后续你会在很多地方要用到 AppID
为了帮助开发者简单和高效地开发和调试微信小程序,我们推出了微信开发者工具
使用小程序开发者工具,开发者可以完成小程序开发调试、代码查看和编辑、小程序预览和发布等功能。
开发者工具下载地址
微信开发者工具下载地址与更新日志 | 微信开放文档微信开发者平台文档https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html
安装如一般软件一样,并没有特别之处
基础开发,我们要选择“JavaScript-基础模板”
开发者工具说明
在微信开发者工具中,我们可以编写代码的同时查看运行结果和调试问题
小程序包含一个描述整体程序的 app
和多个描述各自页面的 page
app
文件 | 必需 | 作用 |
---|---|---|
app.js | 是 | 小程序逻辑 |
app.json | 是 | 小程序公共配置 |
app.wxss | 是 | 小程序公共样式表 |
page
一个小程序页面由四个文件组成,分别是
文件类型 | 必需 | 作用 |
---|---|---|
js | 是 | 页面逻辑 |
wxml | 是 | 页面结构 |
json | 否 | 页面配置 |
wxss | 否 | 页面样式表 |
温馨提示
为了方便开发者减少配置项,描述页面的四个文件必须具有相同的路径与文件名
用于指定小程序由哪些页面组成,每一项都对应一个页面的路径(含文件名) 信息。文件名不需要写文件后缀,框架会自动去寻找对应位置的 .json
, .js
, .wxml
, .wxss
四个文件进行处理
温馨提示
未指定
entryPagePath
时,数组的第一项代表小程序的初始页面(首页)
小程序中新增/减少页面,都需要对app.json
文件中 pages
数组进行修改
默认项目存在两个页面: index
和logs
,对应的app.json
中pages
属性的配置:
"pages": [
"pages/index/index",
"pages/logs/logs"
]
增加页面 news
之后,修改 app.json
文件中的pages
属性(其实页面创建出来,也会自动加载)
"pages": [
"pages/news/news",
"pages/index/index",
"pages/logs/logs"
],
修改pages/news/news.wxml
文件为:
news页面
指定小程序的默认启动路径(首页),在 app.json
配置文件中增加 entryPagePath
{
"entryPagePath": "pages/index/index",
"pages": [
"pages/news/news",
"pages/index/index",
"pages/logs/logs"
],
}
在 app.json
文件中的pages
中直接添加路径,可以自动生成页面
例如:我们增加about
页面路由,可以自动生成about
页面
{
"entryPagePath": "pages/index/index",
"pages": [
"pages/news/news",
"pages/index/index",
"pages/logs/logs",
"pages/about/about"
],
}
window
用于设置小程序的状态栏、导航条、标题、窗口背景色等等
属性 | 类型 | 默认值 | 描述 |
---|---|---|---|
navigationBarBackgroundColor | HexColor | #000000 | 导航栏背景颜色,如 #000000 |
navigationBarTextStyle | string | white | 导航栏标题颜色,仅支持 black / white |
navigationBarTitleText | string | 导航栏标题文字内容 | |
backgroundColor | HexColor | #ffffff | 窗口的背景色 |
backgroundTextStyle | string | dark | 下拉 loading 的样式,仅支持 dark / light |
enablePullDownRefresh | boolean | false | 是否开启全局的下拉刷新。 |
onReachBottomDistance | number | 50 | 页面上拉触底事件触发时距页面底部距离,单位为 px。 |
修改app.json
文件中的window
属性配置
"window": {
"navigationBarBackgroundColor": "#000000",
"navigationBarTextStyle": "white",
"navigationBarTitleText": "第一个小程序",
"backgroundColor": "#000000",
"backgroundTextStyle": "light",
"enablePullDownRefresh":true,
"onReachBottomDistance":50
},
如果小程序是一个多 tab 应用(客户端窗口的底部或顶部有 tab 栏可以切换页面),可以通过 tabBar 配置 tab 切换时显示的对应页面
属性 | 类型 | 必填 | 默认值 | 描述 |
---|---|---|---|---|
list | Array | 是 | tab 的列表,详见 list 属性说明,最少 2 个、最多 5 个 tab |
温馨提示
其中 list 接受一个数组,只能配置最少 2 个、最多 5 个 tab
属性 | 类型 | 必填 | 说明 |
---|---|---|---|
pagePath | string | 是 | 页面路径,必须在 pages 中先定义 |
text | string | 是 | tab 上按钮文字 |
iconPath | string | 否 | 图片路径,icon 大小限制为 40kb,建议尺寸为 81px * 81px,不支持网络图片。 当 position 为 top 时,不显示 icon。 |
selectedIconPath | string | 否 | 选中时的图片路径,icon 大小限制为 40kb,建议尺寸为 81px * 81px,不支持网络图片。 当 position 为 top 时,不显示 icon。 |
在设置iconPath和selectedIconPath属性时:在阿里图库里面下载素材,不是代码
修改app.json
配置文件,增加tabBar
属性配置
"tabBar": {
"list": [
{
"pagePath": "pages/homes/homes",
"text": "首页",
"iconPath": "./images/shouye.png",
"selectedIconPath": "./images/shouye_select.png"
},
{
"pagePath": "pages/gongneng/gongneng",
"text": "功能",
"iconPath": "./images/gongneng.png",
"selectedIconPath": "./images/gongneng_select.png"
},
{
"pagePath": "pages/fujin/fujin",
"text": "附近",
"iconPath": "./images/fujin.png",
"selectedIconPath": "./images/fujin_select.png"
},
{
"pagePath": "pages/gouwuche/gouwuche",
"text": "购物车",
"iconPath": "./images/gouwuche.png",
"selectedIconPath": "./images/gouwuche_select.png"
},
{
"pagePath": "pages/wode/wode",
"text": "我的",
"iconPath": "./images/wode.png",
"selectedIconPath": "./images/wode_select.png"
}
]
},
如果小程序是一个多 tab 应用(客户端窗口的底部或顶部有 tab 栏可以切换页面),可以通过 tabBar 配置项指定 tab 栏的表现,以及 tab 切换时显示的对应页面。
属性 | 类型 | 必填 | 默认值 | 描述 |
---|---|---|---|---|
color | HexColor | 是 | tab 上的文字默认颜色,仅支持十六进制颜色 | |
selectedColor | HexColor | 是 | tab 上的文字选中时的颜色,仅支持十六进制颜色 | |
backgroundColor | HexColor | 是 | tab 的背景色,仅支持十六进制颜色 | |
borderStyle | string | 否 | black | tabbar 上边框的颜色, 仅支持 black / white |
list | Array | 是 | tab 的列表,详见 list 属性说明,最少 2 个、最多 5 个 tab |
|
position | string | 否 | bottom | tabBar 的位置,仅支持 bottom / top |
配置app.json
文件中的tabBar
的属性
"tabBar": {
"color": "#cdcdcd",
"selectedColor": "#d81e06",
"backgroundColor": "#ffffff",
"borderStyle": "black",
"position": "bottom",
"list": [
{
"pagePath": "pages/homes/homes",
"text": "首页",
"iconPath": "./images/shouye.png",
"selectedIconPath": "./images/shouye_select.png"
},
{
"pagePath": "pages/gongneng/gongneng",
"text": "功能",
"iconPath": "./images/gongneng.png",
"selectedIconPath": "./images/gongneng_select.png"
},
{
"pagePath": "pages/fujin/fujin",
"text": "附近",
"iconPath": "./images/fujin.png",
"selectedIconPath": "./images/fujin_select.png"
},
{
"pagePath": "pages/gouwuche/gouwuche",
"text": "购物车",
"iconPath": "./images/gouwuche.png",
"selectedIconPath": "./images/gouwuche_select.png"
},
{
"pagePath": "pages/wode/wode",
"text": "我的",
"iconPath": "./images/wode.png",
"selectedIconPath": "./images/wode_select.png"
}
]
},
小程序根目录下的 app.json
文件用来对微信小程序进行其他全局配置。文件内容为一个 JSON 对象
属性 | 类型 | 必填 | 描述 |
---|---|---|---|
style | string | 否 | 指定使用升级后的 weui 样式 |
sitemapLocation | string | 是 | 指明 sitemap.json 的位置 |
networkTimeout | Object | 否 | 网络超时时间 |
debug | boolean | 否 | 是否开启 debug 模式,默认关闭 |
debugOptions | Object | 否 | 调试相关配置 |
permission | Object | 否 | 小程序接口权限相关设置 |
... | ... | ... | ... |
微信客户端 7.0 开始,UI 界面进行了大改版。小程序也进行了基础组件的样式升级。app.json 中配置 "style": "v2"
可表明启用新版的组件样式
指明 sitemap.json的位置;默认为 'sitemap.json' 即在 app.json 同级目录下名字的 sitemap.json
文件
各类网络请求的超时时间,单位均为毫秒
属性 | 类型 | 必填 | 默认值 | 说明 |
---|---|---|---|---|
request | number | 否 | 60000 | wx.request 的超时时间,单位:毫秒 |
connectSocket | number | 否 | 60000 | wx.connectSocket 的超时时间,单位:毫秒 |
uploadFile | number | 否 | 60000 | wx.uploadFile 的超时时间,单位:毫秒 |
downloadFile | number | 否 | 60000 | wx.downloadFile 的超时时间,单位:毫秒 |
可以在开发者工具中开启 debug
模式,在开发者工具的控制台面板,调试信息以 info
的形式给出,其信息有 Page 的注册,页面路由,数据更新,事件触发等。可以帮助开发者快速定位一些常见的问题
小程序调试相关配置项
属性 | 类型 | 必填 | 默认值 | 描述 |
---|---|---|---|---|
enableFPSPanel | boolean | 否 | false | 是否开启 FPS 面板 |
FPS 面板
为了便于开发者调试渲染层的交互性能,小程序基础库提供了选项开启 FPS 面板,开发者可以实时查看渲染层帧率
开启方式
"debugOptions": {
"enableFPSPanel": true
}
温馨提示
必须在真机上才能看到
小程序接口权限相关设置
例如:小程序定位设置,配置如下
"permission": {
"scope.userLocation": {
"desc": "你的位置信息将用于小程序位置接口的效果展示"
}
}
app.json
文件配置如下 {
"entryPagePath": "pages/index/index",
"pages":[
"pages/index/index",
"pages/logs/logs",
"pages/homes/homes",
"pages/wode/wode",
"pages/fujin/fujin",
"pages/gongneng/gongneng",
"pages/gouwuche/gouwuche"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "Weixin",
"navigationBarTextStyle":"black",
"enablePullDownRefresh": true,
"onReachBottomDistance": 50,
"backgroundColor": "#000"
},
"tabBar": {
"color": "#cdcdcd",
"selectedColor": "#d81e06",
"backgroundColor": "#ffffff",
"borderStyle": "black",
"position": "bottom",
"list": [
{
"pagePath": "pages/homes/homes",
"text": "首页",
"iconPath": "./images/shouye.png",
"selectedIconPath": "./images/shouye_select.png"
},
{
"pagePath": "pages/gongneng/gongneng",
"text": "功能",
"iconPath": "./images/gongneng.png",
"selectedIconPath": "./images/gongneng_select.png"
},
{
"pagePath": "pages/fujin/fujin",
"text": "附近",
"iconPath": "./images/fujin.png",
"selectedIconPath": "./images/fujin_select.png"
},
{
"pagePath": "pages/gouwuche/gouwuche",
"text": "购物车",
"iconPath": "./images/gouwuche.png",
"selectedIconPath": "./images/gouwuche_select.png"
},
{
"pagePath": "pages/wode/wode",
"text": "我的",
"iconPath": "./images/wode.png",
"selectedIconPath": "./images/wode_select.png"
}
]
},
"style": "v2",
"sitemapLocation": "sitemap.json",
"networkTimeout": {
"request": 20000,
"connectSocket": 20000,
"uploadFile": 20000,
"downloadFile": 20000
},
"debug": true,
"debugOptions": {
"enableFPSPanel": true
},
"permission": {
"scope.userLocation": {
"desc": "你的位置信息将用于小程序位置接口的效果展示"
}
}
}
app.json
中的部分配置,也支持对单个页面进行配置
可以在页面对应的 文件.json
文件来对本页面的表现进行配置
属性 | 类型 | 默认值 | 描述 |
---|---|---|---|
navigationBarBackgroundColor | HexColor | #000000 | 导航栏背景颜色,如 #000000 |
navigationBarTextStyle | string | white | 导航栏标题颜色,仅支持 black / white |
navigationBarTitleText | string | 导航栏标题文字内容 | |
backgroundColor | HexColor | #ffffff | 窗口的背景色(开启下拉菜单可以观察) |
backgroundTextStyle | string | dark | 下拉 loading 的样式,仅支持 dark / light |
enablePullDownRefresh | boolean | false | 是否开启当前页面下拉刷新 |
onReachBottomDistance | number | 50 | 页面上拉触底事件触发时距页面底部距离,单位为px。 |
style | string | default | 启用新版的组件样式 |
... | ... | ... | ... |
页面 : 文件.json
文件配置
虽然配置与 app.json
基本一致,但是注意,不在需要添加 window
作为父级
{
"usingComponents": {},
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "首页",
"navigationBarTextStyle":"black",
"enablePullDownRefresh": true,
"onReachBottomDistance": 20,
"backgroundColor": "#ccc",
"style": "v2"
}
在项目根目录的 app.wxss
文件为小程序公的共样式表,相当与CSS初始化文件配置
WXSS (WeiXin Style Sheets)是一套样式语言,用于描述 WXML 的组件样式
WXSS 用来决定 WXML 的组件应该怎么显示
为了适应广大的前端开发者,WXSS 具有 CSS 大部分特性。同时为了更适合开发微信小程序,WXSS 对 CSS 进行了扩充以及修改。
与 CSS 相比,WXSS 扩展的特性有
在 app.wxss
文件中添加样式
text{
color:red;
}
项目中所有的页面的 text
文本都会呈现红色
rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素
设备 | rpx换算px (屏幕宽度/750) | px换算rpx (750/屏幕宽度) |
---|---|---|
iPhone5 | 1rpx = 0.42px | 1px = 2.34rpx |
iPhone6 | 1rpx = 0.5px | 1px = 2rpx |
iPhone6 Plus | 1rpx = 0.552px | 1px = 1.81rpx |
建议: 开发微信小程序时设计师可以用 iPhone6 作为视觉稿的标准。
注意: 在较小的屏幕上不可避免的会有一些毛刺,请在开发时尽量避免这种情况
在news
页面的wxml
文件中增加下列代码
在 app.wxss
文件中,增加box
盒子的样式
.box{
width: 200rpx;
height: 200rpx;
background: red;
}
在iphone5上的效果,元素的宽高是85px
在iphone6上的效果,元素的宽高是100px
使用@import
语句可以导入外联样式表,@import
后跟需要导入的外联样式表的相对路径,用;
表示语句结束
在项目根目录下创建common
文件夹,并创建common.wxss
文件,增加box
的样式
.box{
margin: 50px;
}
在app.wxss
文件中引入common.wxss
文件
@import "./common/common.wxss";
此时,刚刚所创建的box
也加载了引入文件的样式
每个小程序都需要在 app.js
中调用 App
方法注册小程序实例,绑定生命周期回调函数、错误监听和页面不存在监听函数等
生命周期
通俗地理解为“从摇篮到坟墓”(Cradle-to-Grave)的整个过程
属性 | 类型 | 必填 | 说明 |
---|---|---|---|
onLaunch | function | 否 | 生命周期回调——监听小程序初始化。 |
onShow | function | 否 | 生命周期回调——监听小程序启动或切前台。 |
onHide | function | 否 | 生命周期回调——监听小程序切后台。 |
onError | function | 否 | 错误监听函数 |
onPageNotFound | function | 否 | 页面不存在监听函数 |
onThemeChange | function | 否 | 监听系统主题变化 |
我们来修改初始app.js
文件,代码如下
// app.js
App({
onLaunch(options) {
console.log("监听小程序初始化",options);
},
onShow (options) {
console.log("监听小程序启动",options);
},
onHide () {
console.log("监听小程序切后台");
},
onError (msg) {
// 小程序发生脚本错误或 API 调用报错时触发
console.log("错误监听函数",msg)
},
onPageNotFound(res){
console.log("页面不存在监听函数");
},
onThemeChange(){
console.log("系统切换主题时触发");
}
})
整个小程序只有一个 App 实例,是全部页面共享的。开发者可以通过 getApp
方法获取到全局唯一的 App 实例,获取 App 上的数据或调用开发者注册在 App
上的函数。
在 app.js
文件中增加全局属性
// app.js
App({
globalData: {
userInfo: "我是全局属性"
}
})
我们在news.js
文件中读取全局属性
Page({
onLoad(options) {
const appInstance = getApp()
console.log(appInstance.globalData.userInfo) // 我是全局属性
}
})
当然,我们也可以在页面中显示,首先修改news.js
文件
Page({
data:{
message:""
},
onLoad(options) {
const appInstance = getApp()
// 关于this.setData({}),后续会详细讲解,目前我们知道可以给message赋值即可
this.setData({
message:appInstance.globalData.userInfo
})
}
})
修改news.wxml
文件显示内容
{{ message }}
注册小程序中的一个页面。指定页面的生命周期函数
属性 | 类型 | 说明 |
---|---|---|
onLoad | function | 生命周期回调—监听页面加载 |
onShow | function | 生命周期回调—监听页面显示 |
onReady | function | 生命周期回调—监听页面初次渲染完成 |
onHide | function | 生命周期回调—监听页面隐藏 |
onUnload | function | 生命周期回调—监听页面卸载 |
我们在news
页面中的news.js
文件中去增加这些生命周期函数
Page({
data:{
message:""
},
onLoad(options) {
console.log("页面加载");
const appInstance = getApp()
this.setData({
message:appInstance.globalData.userInfo
})
},
onShow() {
console.log("页面显示");
},
onReady() {
console.log("页面初次渲染完成");
},
onHide() {
console.log("页面隐藏");
},
onUnload() {
console.log("页面卸载");
}
})
在不同的生命周期函数,根据业务需求,可以增加业务。例如:我们可以在 onShow
函数中修改data
中的数据
Page({
data:{
hello:"hello"
},
onShow() {
this.setData({
hello:"大家好"
})
}
})
data
是页面第一次渲染使用的初始数据
页面加载时,data
中的数据将会以JSON
字符串的形式由逻辑层传至渲染层,因此data
中的数据必须是可以转成JSON
的类型:字符串,数字,布尔值,对象,数组
我们在news.js
文件中增加data
对象,并增加相应的数据,显示在页面中
// news.js文件
Page({
data:{
hello:"hello",
num:10,
flag:true,
user:{
name:"iwen",
age:20
},
names:["iwen","ime","frank"]
}
})
{{ hello }}
{{ num }}
{{ user.name }}
{{ names[1] }}
setData
函数用于将data
中的数据进行修改,并发送到视图层
我们在news.js
文件中修改data
中的数据,我们可以尝试在onLoad
中修改num
的属性值
Page({
data:{
message:"",
hello:"hello",
num:10,
flag:true,
user:{
name:"iwen",
age:20
},
names:["iwen","ime","frank"]
},
onLoad(options) {
this.setData({
num:20
})
}
})
温馨提示
- 直接修改 this.data 而不调用 this.setData 是无法改变页面的状态的,还会造成数据不一致
- 仅支持设置可 JSON 化的数据
- 单次设置的数据不能超过1024kB,请尽量避免一次设置过多的数据
- 请不要把 data 中任何一项的 value 设为
undefined
,否则这一项将不被设置并可能遗留一些潜在问题
温馨解释
尽管关于
data
和setData
这种操作很神奇,但其实这也是前端最常用的模版数据绑定方案。数据绑定的过程其实不复杂
- 解析语法生成 AST
- 根据 AST 结果生成 DOM
- 将数据绑定更新至模板
其实这些目前我们并不需要去了解,只需要知道,如何使用即可
小程序的主要开发语言是 JavaScript ,小程序的开发同普通的网页开发相比有很大的相似性。但是二者还是多少有些许区别的,例如:小程序提供了一系列的视图组件代替html
中的标签
view
text
image
view
视图容器,用来承载视图块,类似 div
的功能,所以要写在 wxml
视图文件之中
我们在项目中增加一个页面views
,并指定为默认页面
视图1
视图2
view
是块级元素
text
文本1
文本2
文本,承载页面文本信息,类似span
的功能
text
是行内元素
温馨提示
- text 组件内只支持 text 嵌套
- 除了文本节点以外的其他节点都无法长按选中
image
图片。支持 JPG、PNG、SVG、WEBP、GIF 等格式
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
src | string | 否 | 图片资源地址 | |
mode | string | scaleToFill | 否 | 图片裁剪、缩放的模式 |
温馨提示
tip
:image组件默认宽度320px、高度240pxtip
:image组件中二维码/小程序码图片不支持长按识别
mode属性说明
合法值 | 说明 |
---|---|
scaleToFill | 缩放模式,不保持纵横比缩放图片,使图片的宽高完全拉伸至填满 image 元素 |
aspectFit | 缩放模式,保持纵横比缩放图片,使图片的长边能完全显示出来。也就是说,可以完整地将图片显示出来。 |
aspectFill | 缩放模式,保持纵横比缩放图片,只保证图片的短边能完全显示出来。也就是说,图片通常只在水平或垂直方向是完整的,另一个方向将会发生截取。 |
widthFix | 缩放模式,宽度不变,高度自动变化,保持原图宽高比不变 |
heightFix | 缩放模式,高度不变,宽度自动变化,保持原图宽高比不变 |
top | 裁剪模式,不缩放图片,只显示图片的顶部区域 |
bottom | 裁剪模式,不缩放图片,只显示图片的底部区域 |
center | 裁剪模式,不缩放图片,只显示图片的中间区域 |
left | 裁剪模式,不缩放图片,只显示图片的左边区域 |
right | 裁剪模式,不缩放图片,只显示图片的右边区域 |
top left | 裁剪模式,不缩放图片,只显示图片的左上边区域 |
top right | 裁剪模式,不缩放图片,只显示图片的右上边区域 |
bottom left | 裁剪模式,不缩放图片,只显示图片的左下边区域 |
bottom right | 裁剪模式,不缩放图片,只显示图片的右下边区域 |
滑块视图容器(焦点轮播图)
我们增加一个全新的页面swiper
来实现轮播图效果
为了更美观,可以让图片宽度充满全屏,并保持图片不变形
同时设置图片样式充满全屏,因为图片默认大小:宽度320px、高度240px
/* swiper.wxss */
image{
width: 100%;
}
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
indicator-dots | boolean | false | 否 | 是否显示面板指示点 |
indicator-color | color | rgba(0, 0, 0, .3) | 否 | 指示点颜色 |
indicator-active-color | color | #000000 | 否 | 当前选中的指示点颜色 |
autoplay | boolean | false | 否 | 是否自动切换 |
interval | number | 5000 | 否 | 自动切换时间间隔 |
duration | number | 500 | 否 | 滑动动画时长 |
circular | boolean | false | 否 | 是否采用衔接滑动 |
vertical | boolean | false | 否 | 滑动方向是否为纵向 |
我们可以在逻辑文件swiper.js
中动态配置属性值
// swiper.js
Page({
data: {
swiperOptions:{
indicatorDots:true,
indicatorColor:"#fff",
indicatorActiveColor:"#f00",
autoplay:true,
interval:5000,
duration:1000,
circular:true,
vertical:true
}
}
})
可滚动视图区域。可实现容器内元素水平和垂直方向滚动
给容器设置scroll-x
,可实现水平滚动
当然要配合样式实现
/* scroll.wxss */
.scroll-view_H{
/* 规定容器内元素不进行换行 */
white-space: nowrap;
}
.scroll-view-item {
display: inline-block;
width: 100%;
height: 300rpx;
}
.demo-text-1{
background-color: red;
}
.demo-text-2{
background-color: green;
}
.demo-text-3{
background-color: blue;
}
给容器设置scroll-y
,可实现垂直滚动
当然要配合样式实现
/* scroll.wxss */
.scroll-view-item {
width: 100%;
height: 300rpx;
}
.demo-text-1{
background-color: red;
}
.demo-text-2{
background-color: green;
}
.demo-text-3{
background-color: blue;
}
.scroll-view_V{
height: 300rpx;
}
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
scroll-x | boolean | false | 否 | 允许横向滚动 |
scroll-y | boolean | false | 否 | 允许纵向滚动 |
scroll-top | number/string | 否 | 设置竖向滚动条位置 | |
scroll-left | number/string | 否 | 设置横向滚动条位置 | |
refresher-enabled | boolean | false | 否 | 开启自定义下拉刷新 |
图标组件,其实就是字体图标效果,但是这里所提供的只有最常用的几个
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
type | string | 是 | icon的类型,有效值:success, success_no_circle, info, warn, waiting, cancel, download, search, clear | |
size | number/string | 23 | 否 | icon的大小,单位默认为px,2.4.0起支持传入单位(rpx/px),2.21.3起支持传入其余单位(rem 等)。 |
color | string | 否 | icon的颜色,同 css 的color |
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
percent | number | 否 | 百分比0~100 | |
show-info | boolean | false | 否 | 在进度条右侧显示百分比 |
border-radius | number/string | 0 | 否 | 圆角大小 |
font-size | number/string | 16 | 否 | 右侧百分比字体大小 |
stroke-width | number/string | 6 | 否 | 进度条线的宽度 |
activeColor | string | #09BB07 | 否 | 进度条颜色 |
backgroundColor | string | #EBEBEB | 否 | 未选择的进度条的颜色 |
active | boolean | false | 否 | 进度条从左往右的动画 |
duration | number | 30 | 否 | 进度增加1%所需毫秒数 |
表单,将用户输入的信息提交到服务器
小程序的表单与html
的表单基本一致
实现一个基础的登录页面,要用到以下组件
form
表单input
输入框button
按钮创建一个登陆页面login
,在login.wxml
中实现基本结构
为了美观,我们需要在login.wxss
文件中添加样式
/* login.wxss */
.login{
margin-top: 100rpx;
}
input{
border: 1px solid #999;
border-radius: 5px;
margin: 10px;
padding-left: 10px;
height: 70rpx;
}
小程序的 button
按钮与 html
的非常类似,但是小程序的功能要更强大一些
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
type | string | default | 否 | 按钮的样式类型 |
size | string | default | 否 | 按钮的大小 |
plain | boolean | false | 否 | 按钮是否镂空,背景色透明 |
disabled | boolean | false | 否 | 是否禁用 |
loading | boolean | false | 否 | 名称前是否带 loading 图标 |
form-type | string | 否 | 用于 form组件,点击分别会触发form组件的 submit/reset 事件 |
type
属性说明
合法值 | 说明 |
---|---|
primary | 绿色 |
default | 白色 |
warn | 红色 |
size
属性说明
合法值 | 说明 |
---|---|
default | 默认大小 |
mini | 小尺寸 |
输入框是input
, 与html
的输入框类似,但是增加了很多新的功能
为了展示效果,需要配合样式
input{
border: 2px solid #999;
border-radius:10px;
margin:10px;
padding-left:10px;
}
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
value | string | 是 | 输入框的初始内容 | |
placeholder | string | 是 | 输入框为空时占位符 | |
password | boolean | false | 否 | 是否是密码类型 |
disabled | boolean | false | 否 | 是否禁用 |
maxlength | number | 140 | 否 | 最大输入长度,设置为 -1 的时候不限制最大长度 |
focus | boolean | false | 否 | 获取焦点 |
type | string | text | 否 | input 的类型 |
confirm-type | string | done | 否 | 设置键盘右下角按钮的文字,仅在type='text'时生效 |
type
属性详解
合法值 | 说明 |
---|---|
text | 文本输入键盘 |
number | 数字输入键盘 |
idcard | 身份证输入键盘 |
digit | 带小数点的数字键盘 |
nickname | 昵称输入键盘 |
confirm-type
属性详解
合法值 | 说明 |
---|---|
send | 右下角按钮为“发送” |
search | 右下角按钮为“搜索” |
next | 右下角按钮为“下一个” |
go | 右下角按钮为“前往” |
done | 右下角按钮为“完成” |
配合样式更美观
input{
border: 2px solid #999;
border-radius:10px;
margin:10px;
padding-left:10px;
}
从底部弹起的滚动选择器
选择器有很多种类,分别是
selector
")指定mode
属性为selector
,或者默认不指定mode
普通选择器
当前选择:{{array[index]}}
选择器展示效果需要配合逻辑
Page({
data: {
array: ['美国', '中国', '巴西', '日本'],
index: 0
},
bindPickerChange(e) {
this.setData({
index: e.detail.value
})
}
})
multiSelector
")指定mode
属性为multiSelector
多列选择器
当前选择:{{multiArray[0][multiIndex[0]]}},{{multiArray[1][multiIndex[1]]}},{{multiArray[2][multiIndex[2]]}}
Page({
data: {
multiArray: [
['无脊柱动物', '脊柱动物'],
['扁性动物', '线形动物', '环节动物', '软体动物', '节肢动物'],
['猪肉绦虫', '吸血虫']
],
multiIndex: [0, 0, 0],
},
bindMultiPickerChange: function (e) {
this.setData({
multiIndex: e.detail.value
})
}
})
time
")指定mode
属性为time
时间选择器
当前选择: {{time}}
Page({
data: {
time: '12:01'
},
bindTimeChange: function (e) {
console.log('picker发送选择改变,携带值为', e.detail.value)
this.setData({
time: e.detail.value
})
}
})
指定mode
属性为date
日期选择器
当前选择: {{date}}
Page({
data: {
date: '2030-09-01'
},
bindDateChange: function (e) {
console.log('picker发送选择改变,携带值为', e.detail.value)
this.setData({
date: e.detail.value
})
}
})
region
")指定mode
属性为region
省市区选择器
当前选择:{{region[0]}},{{region[1]}},{{region[2]}}
Page({
data: {
region: ['广东省', '广州市', '海珠区']
},
bindRegionChange: function (e) {
console.log('picker发送选择改变,携带值为', e.detail.value)
this.setData({
region: e.detail.value
})
}
})
基础选择器
当前选择的是:{{ Array[index] }}
多列选择器
当前选择的是:
{{ multiArray[0][multiIndex[0]]}},{{multiArray[1][multiIndex[1]]}},{{multiArray[2][multiIndex[2]]}}
时间选择器
当前时间是:
{{ time }}
日期选择器
当前日期是:
{{ date }}
城市选择器
当前城市是:
{{ region[0] }},{{ region[1] }},{{ region[2] }}
// pages/picker/picker.js
Page({
/**
* 页面的初始数据
*/
data: {
Array: ["中国", "美国", "英国", "俄罗斯", "法国", "德国"],
index: 0,
multiArray: [
['无脊柱动物', '脊柱动物'],
['扁性动物', '线形动物', '环节动物', '软体动物', '节肢动物'],
['猪肉绦虫', '吸血虫']
],
multiIndex:[0,0,0],
time:"08:30",
date:"2023-10-01",
region:["甘肃省","兰州市","靖远县"]
},
bindSelectorChange(e) {
this.setData({
index: e.detail.value
})
},
bindMultiSelectorChange(e){
this.setData({
multiIndex:e.detail.value
})
},
bindTimeChange(e){
this.setData({
time:e.detail.value
})
},
bindDateChange(e){
this.setData({
date:e.detail.value
})
},
bindRegionChange(e){
this.setData({
region:e.detail.value
})
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})
/* pages/picker/picker.wxss */
.view{
margin: 20px;
font-size: 18px;
color: #666;
letter-spacing: 2px;
}
.picker{
margin: 20px 20px;
border: 1px solid #999;
border-radius: 20px;
background-color: #f1f1f1;
display: flex;
align-items: center;
justify-content: center;
}
滑动选择器
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
step | number | 1 | 否 | 步长,取值必须大于 0,并且可被(max - min)整除 |
show-value | boolean | false | 否 | 是否显示当前 value |
min | number | 0 | 否 | 最小值 |
max | number | 100 | 否 | 最大值 |
disabled | boolean | false | 否 | 是否禁用 |
value | number | 0 | 否 | 当前取值 |
backgroundColor | color | #e9e9e9 | 否 | 背景条的颜色 |
activeColor | color | #1aad19 | 否 | 已选择的颜色 |
block-color | color | #ffffff | 否 | 滑块的颜色 |
表单是常用的组件,同样,在表单中,也有很多配套的组件
多选项目,与html
复选框基本一致
选中
checked
表示初始状态为选中(true) 或 未选中(false)
配合 checkbox-group
形成一组
读书
打游戏
听音乐
单选项目,与html
单选框基本一致
选中
checked
表示初始状态为选中(true) 或 未选中(false)
配合 radio-group
形成一组
选项1
选项2
选项3
选项4
用来改进表单组件的可用性,与html
的label
基本一致
开关选择器,有着比较美观的展示效果
属性列表
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
checked | boolean | false | 否 | 是否选中 |
disabled | boolean | false | 否 | 是否禁用 |
type | string | switch | 否 | 样式,有效值:switch, checkbox |
color | string | #04BE02 | 否 | switch 的颜色,同 css 的 color |
多行输入框,与html
多行输入框基本一致
为了可见性,我们需要增加样式
textarea{
border: 1px solid red;
}
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
value | string | 否 | 输入框的内容 | |
placeholder | string | 否 | 输入框为空时占位符 | |
disabled | boolean | false | 否 | 是否禁用 |
maxlength | number | 140 | 否 | 最大输入长度,设置为 -1 的时候不限制最大长度 |
focus | boolean | false | 否 | 获取焦点 |
auto-height | boolean | false | 否 | 是否自动增高 |
看电影
看电视
看电影
打游戏
听音乐
选项一
选项二
A:black
B:orange
C:pink
开关选择器
开关选择器
开关选择器
开关选择器
开关选择器
/* pages/otherForm/otherForm.wxss */
textarea{
border: 1px solid #999;
margin: 10px;
}
switch{
margin: 8px;
}
navigator
实现页面之间的跳转
跳转其他页面
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
url | string | 否 | 当前小程序内的跳转链接 | |
open-type | string | navigate | 否 | 跳转方式,默认打开新页面,redirect 在当前页面打开 |
跳转其他页面
在当前页打开
onUnload
在之前的讲解中无法测试,现在有了navigator
,我们可以进行测试了
在 navigator
的属性open-type
设置为redirect
时,我们可以观察输入结果
Page({
onUnload() {
console.log("卸载");
}
})
跳转到新的页面:
在当前页面打开:
// pages/navigator/navigator.js
Page({
/**
* 页面的初始数据
*/
data: {
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
console.log("页面卸载了");
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})
音频
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
id | string | 否 | audio 组件的唯一标识符 | |
src | string | 否 | 要播放音频的资源地址 | |
loop | boolean | false | 否 | 是否循环播放 |
controls | boolean | false | 否 | 是否显示默认控件 |
poster | string | 否 | 默认控件上的音频封面的图片资源地址 | |
name | string | 未知音频 | 否 | 默认控件上的音频名字 |
author | string | 未知作者 | 否 | 默认控件上的作者名字 |
通过修改audio
的属性,切换音乐
Page({
data: {
audioOptions:{
id:"myAudio",
name:"妈妈的话",
author:"zby忠宇",
poster:"https://p2.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg",
src:"https://music.163.com/song/media/outer/url?id=1961763339",
controls:true,
loop:true
}
},
changeMusicHandle(){
this.setData({
audioOptions:{
id:"myAudio",
name:"时光洪流",
author:"程响",
poster:"https://p2.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg",
src:"https://music.163.com/song/media/outer/url?id=1868943615",
controls:true,
loop:true
}
})
}
})
视频
为了美观,我们将视频宽度充满全屏
video{
width: 100%;
}
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
src | string | 是 | 要播放视频的资源地址,支持网络路径、本地临时路径 | |
duration | number | 否 | 指定视频时长 | |
controls | boolean | true | 否 | 是否显示默认播放控件(播放/暂停按钮、播放进度、时间) |
autoplay | boolean | false | 否 | 是否自动播放 |
loop | boolean | false | 否 | 是否循环播放 |
muted | boolean | false | 否 | 是否静音播放 |
initial-time | number | 0 | 否 | 指定视频初始播放位置 |
show-mute-btn | boolean | false | 否 | 是否显示静音按钮 |
danmu-list | Array | 否 | 弹幕列表 | |
danmu-btn | boolean | false | 否 | 是否显示弹幕按钮,只在初始化时有效,不能动态变更 |
enable-danmu | boolean | false | 否 | 是否展示弹幕,只在初始化时有效,不能动态变更 |
// pages/audio/audio.js
Page({
data: {
danmuList: [{
text: '第 1s 出现的弹幕',
color: '#ff0000',
time: 11
}]
},
onReady() {
this.videoContext = wx.createVideoContext('myVideo')
},
sendDanmuHandle() {
this.videoContext.sendDanmu({
text: "真好看",
color: "#00ff00"
})
}
})
系统相机。扫码二维码功能
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
mode | string | normal | 否 | 应用模式,只在初始化时有效,不能动态变更 normal:相机模式 scanCode:扫码模式 |
device-position | string | back | 否 | 摄像头朝向 front:前置 back:后置 |
flash | string | auto | 否 | 闪光灯,值为 auto , on, off |
预览
属性说明
Page({
data:{
src:""
},
takePhotoHandle() {
const ctx = wx.createCameraContext()
ctx.takePhoto({
quality: 'high',
success: (res) => {
this.setData({
src: res.tempImagePath
})
}
})
}
})
地图,小程序地图实现功能相对比基础一些,如果要实现完整的地图能力,请参考腾讯地图
温馨提示
腾讯地图:微信小程序LBS解决方案 | 腾讯位置服务腾讯地图开放平台为各类应用厂商和开发者提供基于腾讯地图的地理位置服务和解决方案;有针对Web应用的JavaScript API, 适合手机端Native APP的各种SDK, WebService接口和各类地图API等。https://lbs.qq.com/product/miniapp/home/
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
longitude | number | 是 | 中心经度 | |
latitude | number | 是 | 中心纬度 | |
scale | number | 16 | 否 | 缩放级别,取值范围为3-20 |
min-scale | number | 3 | 否 | 最小缩放级别 |
max-scale | number | 20 | 否 | 最大缩放级别 |
// pages/map/map.js
Page({
data: {
latitude: 23.099994,
longitude: 113.324520,
}
})
在组件中绑定一个事件处理函数
Click me!
// pages/event/event.js
Page({
tapName(){
console.log("点击");
}
})
在小程序中,也具有事件对象event
Page({
tapName(e){
console.log(e);
}
})
属性 | 类型 | 说明 |
---|---|---|
type | String | 事件类型 |
timeStamp | Integer | 事件生成时的时间戳 |
target | Object | 触发事件的组件的一些属性值集合 |
currentTarget | Object | 当前组件的一些属性值集合 |
mark | Object | 事件标记数据 |
detail | Object | 额外的信息 |
touches | Array | 触摸事件,当前停留在屏幕中的触摸点信息的数组 |
changedTouches | Array | 触摸事件,当前变化的触摸点信息的数组 |
事件分为冒泡事件和非冒泡事件:
当一个组件上的事件被触发后,该事件会向父节点传递
// pages/event/event.js
Page({
bindParentHandle(){
console.log("冒泡事件:父级事件");
},
bindChildHandle(){
console.log("冒泡事件:子级事件");
}
})
当一个组件上的事件被触发后,该事件不会向父节点传递
// pages/event/event.js
Page({
catchParentHandle(){
console.log("非冒泡事件:父级事件");
},
catchChildHandle(){
console.log("非冒泡事件:子级事件");
}
})
在微信小程序中,事件有很多中类型,通过bind
和catch
与下面的类型组合产生不同类型的事件
类型 | 触发条件 |
---|---|
touchstart | 手指触摸动作开始 |
touchmove | 手指触摸后移动 |
touchcancel | 手指触摸动作被打断,如来电提醒,弹窗 |
touchend | 手指触摸动作结束 |
tap | 手指触摸后马上离开 |
longpress | 手指触摸后,超过350ms再离开,如果指定了事件回调函数并触发了这个事件,tap事件将不被触发 |
longtap | 手指触摸后,超过350ms再离开(推荐使用 longpress 事件代替) |
// pages/event_type/event_type.js
Page({
/**
* 页面的初始数据
*/
data: {
},
/*
事件分类:
冒泡事件:bindtap => bind tap
非冒泡事件:catchtap => catch tap
*/
bindParentHandle() {
console.log("冒泡事件:父级事件");
},
bindChildHandle() {
console.log("冒泡事件:子级事件");
},
catchParentHandle() {
console.log("非冒泡事件:父级事件");
},
catchChildHandle() {
console.log("非冒泡事件:子级事件");
},
bindTouchStart() {
console.log("bind TouchStart");
},
catchTouchStart() {
console.log("catch TouchStart");
},
bindTouchEnd() {
console.log("bind TouchEnd");
},
catchTouchEnd() {
console.log("catch TouchEnd");
},
bindTouchMove() {
console.log("bind TouchMove");
},
catchTouchMove() {
console.log("catch TouchMove");
},
bindTouchCancel() {
console.log("bind TouchCancel");
},
catchTouchCancel() {
console.log("catch TouchCancel");
},
bindLongPress() {
console.log("bind LongPress");
},
catchLongPress() {
console.log("catch LongPress");
}
})
事件在触发的过程中,我们可以携带参数
主要有两种方式:
event
对象中的currentTarget
读取携带参数event
对象中的mark
读取携带参数currentTarget
携带参数在组件节点中可以附加一些自定义数据。这样,在事件中可以获取这些自定义的节点数据,用于事件的逻辑处理。
currentTarget:携带参数
// pages/event/event.js
Page({
bindCurrentTarget(e){
console.log(e);
console.log(e.currentTarget.dataset.id);
}
})
温馨提示
在
wxml
中添加数据的时候,必须在自定义属性前添加data-*
mark
携带参数可以使用 mark
来识别具体触发事件的 target 节点。此外, mark
还可以用于承载一些自定义数据(类似于 dataset
)。
当事件触发时,事件冒泡路径上所有的 mark
会被合并,并返回给事件回调函数。(即使事件不是冒泡事件,也会 mark
。)
// pages/event/event.js
Page({
bindParentMark(e){
console.log(e.mark);
},
bindChildMark(e){
console.log(e.mark);
}
})
小程序提供了在wxml
模板中,使用条件渲染
wx:if
wx:else
wx:elif
hidden
在小程序中,使用 wx:if=""
来判断是否需要渲染该代码块
我是张杰
// pages/if/if.js
Page({
/**
* 页面的初始数据
*/
data: {
flag:true
}
})
有wx:if
匹配的同时还有wx:else
我是张杰
我是邓超
// pages/if/if.js
Page({
/**
* 页面的初始数据
*/
data: {
flag:true
},
bindNext(){
this.setData({
flag:false
})
}
})
如同在javascript
中,单纯的if...else
是不够用的,所以引入了elif
1
2
3
未知
// pages/if/if.js
Page({
/**
* 页面的初始数据
*/
data: {
length:4
}
})
hidden
与wx:if
类似,同样可以来判断是否需要渲染该代码块
老公,可以给我买个苹果xx嘛
wx:if
vs hidden
区别因为 wx:if
之中的模板也可能包含数据绑定,所以当 wx:if
的条件值切换时,框架有一个局部渲染的过程,因为它会确保条件块在切换时销毁或重新渲染。
同时 wx:if
也是惰性的,如果在初始渲染条件为 false
,框架什么也不做,在条件第一次变成真的时候才开始局部渲染。
相比之下,hidden
就简单的多,组件始终会被渲染,只是简单的基于CSS控制显示与隐藏。
一般来说,wx:if
有更高的切换消耗而 hidden
有更高的初始渲染消耗。因此,如果需要频繁切换的情景下,用 hidden
更好,如果在运行时条件不大可能改变则 wx:if
较好
在组件上使用 wx:for
控制属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件
默认数组的当前项的下标变量名默认为 index
,数组当前项的变量名默认为 item
{{ item }}
Page({
data: {
users:["itbaizhan","sxt"]
}
})
使用 wx:for-item
可以指定数组当前元素的变量名
使用 wx:for-index
可以指定数组当前下标的变量名
{{ user }}-{{ ids }}
Page({
data: {
users:["itbaizhan","sxt"]
}
})
网络请求拿到的数据是json
格式,相对比要复杂一些
{{ item.name }}
{{ item.description }}
{{ item.price }}
{{ item.city }}
Page({
data: {
result: [{
"id": 1,
"name": "美食-甜豆干",
"pic": "http://iwenwiki.com:3002/images/goods/1.jpg",
"description": "津津卤汁豆腐干苏州特产豆干零食素食老字号食品豆制品小吃90g*10",
"price": "39.90",
"type": 0,
"buynum": "435",
"city": "北京"
},
{
"id": 2,
"name": "好欢螺螺蛳粉300g*6袋",
"pic": "http://iwenwiki.com:3002/images/goods/2.jpg",
"description": "好欢螺螺蛳粉300g*6袋柳州特产螺狮粉美食螺丝粉煮水方便面酸辣粉",
"price": "69.99",
"type": 0,
"buynum": "3333",
"city": "北京"
}
]
}
})
如果列表中项目的位置会动态改变或者有新的项目添加到列表中,并且希望列表中的项目保持自己的特征和状态,需要使用 wx:key
来指定列表中项目的唯一的标识符
当数据改变触发渲染层重新渲染的时候,会校正带有 key 的组件,框架会确保他们被重新排序,而不是重新创建,以确保使组件保持自身的状态,并且提高列表渲染时的效率
温馨提示
如不提供
wx:key
,会报一个warning
, 如果明确知道该列表是静态,或者不必关注其顺序,可以选择忽略
{{item}}--{{index}}
{{user}}--{{id}}
id是:{{ item.id }}
描述:{{ item.description }}
价格:{{ item.price }}
数量:{{ item.buynum }}
城市:{{ item.city }}
// pages/list/list.js
Page({
/**
* 页面的初始数据
*/
data: {
list: ["张杰", "周杰伦", "古力娜扎", "邓超", "谢霆锋", "赵丽颖"],
user: ["张杰", "周杰伦", "古力娜扎", "邓超", "谢霆锋", "赵丽颖"],
result: [{
"id": 1,
"name": "美食-甜豆干",
"pic": "http://iwenwiki.com:3002/images/goods/1.jpg",
"description": "津津卤汁豆腐干苏州特产豆干零食素食老字号食品豆制品小吃90g*10",
"price": "39.90",
"type": 0,
"buynum": "435",
"city": "北京"
},
{
"id": 2,
"name": "好欢螺螺蛳粉300g*6袋",
"pic": "http://iwenwiki.com:3002/images/goods/2.jpg",
"description": "好欢螺螺蛳粉300g*6袋柳州特产螺狮粉美食螺丝粉煮水方便面酸辣粉",
"price": "69.99",
"type": 0,
"buynum": "3333",
"city": "北京"
}
]
},
bindHandle(){
this.setData({
// concat:合并数据
result:this.data.result.concat({
"id": 3,
"name": "美食-红烧鸡头",
"pic": "http://iwenwiki.com:3002/images/goods/1.jpg",
"description": "红烧鸡头苏州特产90g*10",
"price": "99.9",
"type": 0,
"buynum": "22",
"city": "苏州"
})
})
}
})
列表渲染的应用场景,所有需要重复视图的地方都可以使用列表渲染,例如:swiper
// pages/swiper/swiper.js
Page({
/**
* 页面的初始数据
*/
data: {
swiperData:[
"../../images/1.jpg",
"../../images/2.jpg",
"../../images/3.jpg",
"../../images/4.jpg",
"../../images/5.jpg",
"../../images/6.jpg"
]
},
})
WXML提供模板(template),可以在模板中定义代码片段,然后在不同的地方调用
使用 name 属性,作为模板的名字。然后在内定义代码片段
{{ text }}
.text{
color: red;
}
使用 is 属性,声明需要的使用的模板,然后将模板所需要的 data 传入
当然也需要引入样式,来丰富模板
引入模板样式到页面
@import "./list/list.wxss";
在微信小程序的项目中,同一个项目一般风格统一化,所以相同的页面效果也是常见的,此时模板的使用率就大大增多了。例如最常见的列表效果
{{item.name}}
.foods{
width: 100%;
}
image{
width: 100%;
}
text{
font-size: 22px;
color: rgb(45, 49, 38);
letter-spacing: 2px;
display: block;
margin: 8px;
}
// pages/template/template.js
Page({
/**
* 页面的初始数据
*/
data: {
foods: [{
"id": 1,
"name": "美食-甜豆干",
"pic": "http://iwenwiki.com:3002/images/goods/1.jpg"
},
{
"id": 2,
"name": "好欢螺螺蛳粉300g*6袋",
"pic": "http://iwenwiki.com:3002/images/goods/2.jpg"
},
{
"id": 3,
"name": "良品铺子-肉松饼380gx2袋",
"pic": "http://iwenwiki.com:3002/images/goods/3.webp"
}
]
},
})
@import "./list/list.wxss";
保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面。
温馨提示
程序中页面栈最多十层
可以从pageA
页面通过点击事件跳转到pageB
页面
食物页面
// pages/foods/foods.js
Page({
toFootsPages(){
wx.navigateTo({
url: '/pages/foots/foots',
})
}
})
鞋类页面
A页面——>
食物页面
// pages/foods/foods.js
Page({
toFootsPages(){
wx.navigateTo({
url: '/pages/foots/foots?name=qiaodan',
})
}
})
B页面——>
鞋类页面
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
console.log(options.name);
},
})
关闭当前页面,返回上一页面
A页面——>
食物页面
// pages/foods/foods.js
Page({
toFootsPages(){
wx.navigateTo({
url: '/pages/foots/foots?name=qiaodan',
})
}
})
B页面——>
鞋类页面
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
console.log(options.name);
},
backFoodPage(){
wx.navigateBack({
url:"/pages/foods/foods"
})
}
})
关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面
redirectTo
和navigateTo
最大的区别就是前者无法在返回之前的页面,也就是在页面栈中不存在之前的页面了
A页面 ---------->
pageA页面
// pages/pageA/pageA.js
Page({
redirectB(){
wx.redirectTo({
url: '/pages/pageB/pageB',
})
}
})
B页面 ---------->
pageB页面
温馨提示
这里的参数携带依然是生效的!
A页面 ---------->
pageA页面
// pages/pageA/pageA.js
Page({
redirectB(){
wx.redirectTo({
url: '/pages/pageB/pageB?name=hello',
})
}
})
B页面 ---------->
pageB页面
// pages/pageB/pageB.js
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
console.log(options.name);
},
})
关闭所有页面,打开到应用内的某个页面
B页面 ---------->
pageB页面
// pages/pageB/pageB.js
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
console.log(options.name);
},
bindC(){
wx.navigateTo({
url: '/pages/pageC/pageC',
})
}
})
C页面 ---------->
页面pageC
// pages/pageC/pageC.js
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
bindD(){
wx.navigateTo({
url: '/pages/pageD/pageD',
})
}
})
D页面 ---------->
页面pageD
B页面 ---------->
pageB页面
// pages/pageB/pageB.js
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
console.log(options.name);
},
bindC(){
wx.navigateTo({
url: '/pages/pageC/pageC',
})
}
})
C页面 ---------->
页面pageC
// pages/pageC/pageC.js
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
bindD(){
wx.redirectTo({
url: '/pages/pageD/pageD',
})
}
})
D页面 ---------->
页面pageD
注意:(redirectTo):打开新页面,不保留原来的页面
跳转从B->C->D,从D返回的时候直接到了B页面
B页面 ---------->
pageB页面
// pages/pageB/pageB.js
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
console.log(options.name);
},
bindC(){
wx.navigateTo({
url: '/pages/pageC/pageC',
})
}
})
C页面 ---------->
页面pageC
// pages/pageC/pageC.js
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
bindD(){
wx.reLaunch({
url: '/pages/pageD/pageD',
})
}
})
D页面 ---------->
页面pageD
注意:(reLaunch):关闭所有的页面,打开新页面
跳转从B->C->D,从D返回的时候直接到了B页面
跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面
tabbar
"tabBar": {
"color": "#cdcdcd",
"selectedColor": "#d81e06",
"backgroundColor": "#ffffff",
"borderStyle": "black",
"position": "bottom",
"list": [{
"pagePath": "pages/homes/homes",
"text": "首页",
"iconPath": "./images/shouye.png",
"selectedIconPath": "./images/shouye_select.png"
},
{
"pagePath": "pages/gongneng/gongneng",
"text": "功能",
"iconPath": "./images/gongneng.png",
"selectedIconPath": "./images/gongneng_select.png"
},
{
"pagePath": "pages/fujin/fujin",
"text": "附近",
"iconPath": "./images/fujin.png",
"selectedIconPath": "./images/fujin_select.png"
},
{
"pagePath": "pages/gouwuche/gouwuche",
"text": "购物车",
"iconPath": "./images/gouwuche.png",
"selectedIconPath": "./images/gouwuche_select.png"
},
{
"pagePath": "pages/wode/wode",
"text": "我的",
"iconPath": "./images/wode.png",
"selectedIconPath": "./images/wode_select.png"
}
]
},
页面pageD
// pages/pageD/pageD.js
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
toTabar(){
wx.switchTab({
url: '/pages/homes/homes',
})
}
})
显示消息提示框,给出用户提示,注意该提示框是无焦点的
// pages/toast/toast.js
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
wx.showToast({
title: '消息提示框',
icon:"success",
image:"../../images/shouye.png",
duration:3000,
mask:false
})
},
messageToast(){
wx.showToast({
title: '页面跳转失败',
icon:"error",
duration:3000,
mask:false
})
}
})
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
title | string | 是 | 提示的内容 | |
icon | string | success | 否 | 图标 |
image | string | 否 | 自定义图标的本地路径,image 的优先级高于 icon | |
duration | number | 1500 | 否 | 提示的延迟时间 |
mask | boolean | false | 否 | 是否显示透明蒙层,防止触摸穿透 |
合法值 | 说明 |
---|---|
success | 显示成功图标,此时 title 文本最多显示 7 个汉字长度 |
error | 显示失败图标,此时 title 文本最多显示 7 个汉字长度 |
loading | 显示加载图标,此时 title 文本最多显示 7 个汉字长度 |
none | 不显示图标,此时 title 文本最多可显示两行 |
隐藏消息提示框
显示 loading 提示框。需主动调用 wx.hideLoading 才能关闭提示框
Page({
onLoad(options) {
wx.showLoading({
title: '加载中'
})
}
})
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
title | string | 是 | 提示的内容 | |
mask | boolean | false | 否 | 是否显示透明蒙层,防止触摸穿透 |
// pages/loading/loading.js
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
wx.showLoading({
title: '请稍等...',
mask:false
}),
// 定时器模拟获取数据
setTimeout(() => {
wx.hideLoading();
}, 3000);
}
})
显示模态对话框,其实就是可以进行交互了
// pages/modal/modal.js
Page({
clickModal(){
wx.showModal({
title: '提示',
content: '模拟对话框',
complete: (res) => {
if (res.cancel) {
console.log("点击了取消")
}
if (res.confirm) {
console.log("点击了确定")
}
}
})
}
})
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
title | string | 否 | 提示的标题 | |
content | string | 否 | 提示的内容 | |
showCancel | boolean | true | 否 | 是否显示取消按钮 |
cancelText | string | 取消 | 否 | 取消按钮的文字,最多 4 个字符 |
cancelColor | string | #000000 | 否 | 取消按钮的文字颜色,必须是 16 进制格式的颜色字符串 |
confirmText | string | 确定 | 否 | 确认按钮的文字,最多 4 个字符 |
confirmColor | string | #576B95 | 否 | 确认按钮的文字颜色,必须是 16 进制格式的颜色字符串 |
editable | boolean | false | 否 | 是否显示输入框 |
placeholderText | string | 否 | 显示输入框时的提示文本 |
showCancel boolean true 否 是否显示取消按钮
// pages/modal/modal.js
Page({
clickModal(){
wx.showModal({
title: '提示',
content: '模拟对话框',
showCancel:false,
complete: (res) => {
if (res.cancel) {
console.log("点击了取消")
}
if (res.confirm) {
console.log("点击了确定")
}
}
})
}
})
cancelText string 取消 否 取消按钮的文字,最多 4 个字符 cancelColor string #000000 否 取消按钮的文字颜色,必须是 16 进制格式的颜色字符串 confirmText string 确定 否 确认按钮的文字,最多 4 个字符 confirmColor string #576B95 否 确认按钮的文字颜色,必须是 16 进制格式的颜色字符串
// pages/modal/modal.js
Page({
clickModal(){
wx.showModal({
title: '提示',
content: '模拟对话框',
showCancel:true,
cancelText:"残忍拒绝",
cancelColor:"red",
confirmText:"欣然接受",
confirmColor:"green",
complete: (res) => {
if (res.cancel) {
console.log("点击了取消")
}
if (res.confirm) {
console.log("点击了确定")
}
}
})
}
})
editable boolean false 否 是否显示输入框 placeholderText string 否 显示输入框时的提示文本
// pages/modal/modal.js
Page({
clickModal(){
wx.showModal({
title: '提示',
// content: '模拟对话框',
showCancel:true,
cancelText:"残忍拒绝",
cancelColor:"red",
confirmText:"欣然接受",
confirmColor:"green",
editable:true,
placeholderText:"请输入内容",
complete: (res) => {
if (res.cancel) {
console.log("点击了取消")
}
if (res.confirm) {
console.log("点击了确定")
}
}
})
}
})
object.success 回调函数
属性 | 类型 | 说明 |
---|---|---|
content | string | editable 为 true 时,用户输入的文本 |
confirm | boolean | 为 true 时,表示用户点击了确定按钮 |
cancel | boolean | 为 true 时,表示用户点击了取消 |
显示操作菜单,菜单会从底部弹出
// pages/actionSheet/actionSheet.js
Page({
data: {
},
clickActionSheetHandle(){
wx.showActionSheet({
itemList: ["北京","上海","天津","广州","深圳"],
itemColor:"green",
success(res){
console.log("成功获取");
},
fail(msg){
console.log("失败");
}
})
}
})
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
itemList | Array. | 是 | 按钮的文字数组,数组长度最大为 6 | |
itemColor | string | #000000 | 否 | 按钮的文字颜色 |
success | function | 否 | 接口调用成功的回调函数 | |
fail | function | 否 | 接口调用失败的回调函数 |
object.success 回调函数
属性 | 类型 | 说明 |
---|---|---|
tapIndex | number | 用户点击的按钮序号,从上到下的顺序,从0开始 |
// pages/actionSheet/actionSheet.js
Page({
/**
* 页面的初始数据
*/
data: {
citys:["北京","上海","天津","广州","深圳"]
},
clickActionSheetHandle(){
var that = this;
wx.showActionSheet({
itemList: this.data.citys,
itemColor:"green",
success(res){
console.log("成功获取:"+ that.data.citys[res.tapIndex]);
},
fail(msg){
console.log("失败"+msg.errMsg);
}
})
}
})
在微信小程序中,我们可以通过逻辑动态设置导航栏
方法 | 描述 |
---|---|
showNavigationBarLoading | 在当前页面显示导航条加载动画 |
hideNavigationBarLoading | 在当前页面隐藏导航条加载动画 |
setNavigationBarTitle | 动态设置当前页面的标题 |
hideHomeButton | 隐藏返回首页按钮。当用户打开的小程序最底层页面是非首页时,默认展示“返回首页”按钮,开发者可在页面 onShow 中调用 hideHomeButton 进行隐藏 |
// pages/nav/nav.js
Page({
showNav(){
wx.showNavigationBarLoading()
},
hideNav(){
wx.hideNavigationBarLoading()
},
setPageTitle(){
wx.setNavigationBarTitle({
title: '测试修改页面名称',
})
},
toMyPages(){
wx.navigateTo({
url: '/pages/myPages/myPages',
})
}
})
myPages页面
// pages/myPages/myPages.js
Page({
/**
* 生命周期函数--监听页面显示
*/
onShow() {
wx.hideHomeButton()
},
})
发起 HTTPS 网络请求,从后端获取数据,显示在页面之上
通过wx.request
请求数据
Page({
onLoad(options) {
wx.request({
url: 'https://iwenwiki.com/api/blueberrypai/getChengpinDetails.php',
success(res) {
console.log(res.data)
}
})
}
})
温馨提示
在小程序中使用网络相关的 API 时,需要注意下列问题,请开发者提前了解
- 每个微信小程序需要事先设置通讯域名,小程序只可以跟指定的域名进行网络通信(生产环境)
- 通过开发者工具配置:“不效验合法域名” (开发环境)
服务器域名请在 「小程序后台 - 开发 - 开发设置 - 服务器域名」 中进行配置
小程序开发者工具:详情 - 本地设置 - 勾选 “不效验合法域名” 选项
{{ item.title }}
// pages/request/request.js
Page({
/**
* 页面的初始数据
*/
data: {
chengpinDetails:[]
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
var that = this;
wx.request({
url: 'https://iwenwiki.com/api/blueberrypai/getChengpinDetails.php',
success(res){
console.log(res.data);
that.adapterView(res);
}
})
},
// 视图渲染
adapterView(res){
this.setData({
chengpinDetails:res.data.chengpinDetails
})
}
})
我们了解了基本的网络请求之后,在考虑在网络请求中的属性如何使用。例如:如何传递参数?
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
url | string | 是 | 开发者服务器接口地址 | |
data | string/object/ArrayBuffer | 否 | 请求的参数 | |
header | Object | 否 | 设置请求的 header,header 中不能设置 Referer。 content-type 默认为 application/json |
|
timeout | number | 否 | 超时时间,单位为毫秒。默认值为 60000 | |
method | string | GET | 否 | HTTP 请求方法 常用的方式 GET和POST |
success | function | 否 | 接口调用成功的回调函数 | |
fail | function | 否 | 接口调用失败的回调函数 | |
complete | function | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) |
// pages/allrequest/allrequest.js
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
wx.request({
url: 'http://iwenwiki.com:3002/api/foods/list',
method: "GET",
data: {
city: "北京"
},
header: {
'content-type': 'application/json'
},
timeout:30000,
success(res){
console.log(res);
},
fail(msg){
console.log(msg);
},
complete(){
console.log("请求成功");
}
})
},
})
封装网路请求是为了日后使用更加方便
封装完成之后在页面引用
const {request} = require("../../utils/request")
Page({
/**
* 页面的初始数据
*/
data: {
result:""
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
request("http://iwenwiki.com:3002/api/foods/list","GET",{city:"北京"}).then(res =>{
console.log(res);
this.setData({
result:res.data.data.result
})
})
},
})
{{item.name}}
下拉刷新是我们在移动端所见的最多效果,所有用户希望看到的最新数据的解决方案基本上都会选择”下拉刷新“方式实现。例如:微信的朋友圈
第一步:json
文件配置下拉刷新
{
"enablePullDownRefresh": true,
"backgroundColor": "#f1f1f1",
"backgroundTextStyle":"dark"
}
第二步:实现下拉刷新逻辑
// pages/pullDown/pullDown.js
Page({
/**
* 页面的初始数据
*/
data: {
list:[1,2,3,4,5,6]
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
setTimeout(() => {
this.setData({
list:[7,8,9,10,11,12]
})
wx.stopPullDownRefresh();
}, 2000);
},
})
第三步:渲染视图
{{item}}
第四步:设计样式
page{
background-color: #fff;
}
.root{
padding: 18px;
}
.view{
width: 100%;
height: 50PX;
border-bottom: 1px solid #cccccc;
line-height: 50px;
}
掌握了基础的下拉刷新之后,我们要做的就是真实的应用场景,我们通过网络请求获取数据,并渲染到页面上
第一步:json
文件配置下拉刷新
{
"enablePullDownRefresh": true,
"backgroundColor": "#f1f1f1",
"backgroundTextStyle":"dark"
}
第二步:实现下拉刷新逻辑
const {request} = require("../../utils/request.js")
Page({
/**
* 页面的初始数据
*/
data: {
page:1,
list:[]
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.http(this.data.page)
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
this.setData({
page:this.data.page += 1
})
this.http(this.data.page)
},
// 网络请求
http(page) {
request("http://iwenwiki.com:3002/api/foods/list", "GET", {
city: "北京",
page: page
}).then((res) => {
console.log(res.data);
if(!res.data.msg){
this.setData({
list:res.data.data.result
})
}else{
wx.showToast({
title: "暂无数据",
})
}
wx.stopPullDownRefresh()
})
}
})
第三步:渲染视图
{{ item.name }}
第四步:设计样式
page{
background: #f1f1f1;
}
.root{
padding: 10px;
}
.item{
height: 80px;
margin: 5px 0;
background: #fff;
line-height: 100px;
padding: 10px;
}
image{
width: 80px;
height: 80px;
}
text{
height: 80px;
padding-left: 10px;
position: absolute;
line-height: 80px;
}
"下拉刷新"是为了更新数据,"上拉加载"是为了增加数据,也是页面最常见的效果。例如:微信朋友圈
第一步:json
文件配置(可选项)
{
"onReachBottomDistance":50
}
第二步:实现上拉加载逻辑
// pages/bottom/bottom.js
Page({
/**
* 页面的初始数据
*/
data: {
list:[1,2,3,4,5]
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
this.setData({
list:this.data.list.concat([6,7,8,9,10,11])
})
}
})
第三步:渲染页面
{{ item }}
第四步:样式加载
.item{
height: 200px;
}
text{
font-size: 30px;
}
掌握了基础的上拉加载之后,我们要做的就是真实的应用场景,我们通过网络请求获取数据,并渲染到页面上
第一步:json
文件配置(可选项)
{
"onReachBottomDistance":50
}
第二步:实现上拉加载逻辑
const { request} = require("../../utils/request.js")
Page({
/**
* 页面的初始数据
*/
data: {
page:1,
list:[]
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.http(this.data.page)
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
this.setData({
page:this.data.page += 1
})
this.http(this.data.page)
},
// 上拉加载网络请求
http(page) {
request("http://iwenwiki.com:3002/api/foods/list", "GET", {
city: "北京",
page: page
}).then(res =>{
console.log(res.data);
if(!res.data.msg){
this.setData({
list:this.data.list.concat(res.data.data.result)
})
}else{
wx.showToast({
title: '数据已加载完',
})
}
})
}
})
第三步:渲染页面
{{ item.name }}
第四步:样式加载
page{
background: #f1f1f1;
}
.root{
padding: 10px;
}
.item{
height: 80px;
margin: 5px 0;
background: #fff;
line-height: 100px;
padding: 10px;
}
image{
width: 80px;
height: 80px;
}
text{
height: 80px;
padding-left: 10px;
position: absolute;
line-height: 80px;
}
.container{
margin: 5px;
margin-top: 100px;
}
.container .search{
height: 40px;
border: 2px solid #C20C0C;
padding-left: 10px;
}
.container .btn{
margin-top: 5px;
}
// pages/searchmusic/searchmusic.js
Page({
/**
* 页面的初始数据
*/
data: {
search:""
},
bindKeyInput(e){
console.log(e);
this.setData({
search:e.detail.value
})
},
bindgotoList(){
wx.navigateTo({
url: '/pages/musiclist/musiclist?search=' + this.data.search,
})
}
})
{{ item.name }}
{{ item.artists[0].name }}
page{
background: #f1f1f1;
}
.container{
margin: 5px;
}
.item{
height: 50px;
background: #fff;
margin: 5px;
line-height: 50px;
padding-left: 10px;
}
.author{
font-size: 12px;
margin-left: 20px;
color: #999;
}
const {request} = require("../../utils/request.js")
Page({
data: {
songs:[],
keywords:"",
limit:100,
offset:1
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
console.log(options.search);
this.setData({
keywords:options.search
})
this.http(options.search,this.data.limit,this.data.offset)
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
this.setData({
offset:this.data.offset += 100
})
this.http(this.data.keywords,this.data.limit,this.data.offset)
},
http(keywords,limit,offset){
request("http://iwenwiki.com:3000/search","GET",{
// 关键字
keywords:keywords,
// 返回歌曲数量
limit:limit,
// 偏移量
offset:offset
}).then(res =>{
console.log(res.data);
if(res.data.result.songs){
this.setData({
songs:this.data.songs.concat(res.data.result.songs)
})
}else{
wx.showToast({
title: '数据加载完了',
})
}
})
}
})
{{ item.name }}
{{ item.artists[0].name }}
const {request} = require("../../utils/request.js")
Page({
data: {
songs:[],
keywords:"",
limit:100,
offset:1
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
console.log(options.search);
this.setData({
keywords:options.search
})
this.http(options.search,this.data.limit,this.data.offset)
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
this.setData({
offset:this.data.offset += 100
})
this.http(this.data.keywords,this.data.limit,this.data.offset)
},
http(keywords,limit,offset){
request("http://iwenwiki.com:3000/search","GET",{
// 关键字
keywords:keywords,
// 返回歌曲数量
limit:limit,
// 偏移量
offset:offset
}).then(res =>{
console.log(res.data);
if(res.data.result.songs){
this.setData({
songs:this.data.songs.concat(res.data.result.songs)
})
}else{
wx.showToast({
title: '数据加载完了',
})
}
})
},
toPlayMusic(e){
console.log(e.currentTarget.dataset.id);
const {id,name,author} = e.currentTarget.dataset
wx.navigateTo({
url: '/pages/playmusic/playmusic?id=' + id + "&name=" + name + "&author=" + author
})
}
})
// pages/playmusic/playmusic.js
Page({
/**
* 页面的初始数据
*/
data: {
name:"",
author:"",
poster:"",
src:""
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
console.log(options.id);
this.setData({
src:"https://music.163.com/song/media/outer/url?id=" + options.id,
name:options.name,
author:options.author,
poster:"https://p2.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg"
})
}
})
在小程序中,文件与文件之间是如何管理的呢?小程序提供了"模块化"解决方案
module.exports
require
我们可以使用module.exports
导出,并且使用require
导入
// hello.js
const num = 10;
function hello(){
return "hello"
}
// module.exports.num = num;
// module.exports.hello = hello;
module.exports = {
hello,
num
}
// index.js
const { num,hello } = require("./modules/hello.js")
Page({
onLoad(options) {
console.log(num);
console.log(hello());
}
})
将本地资源上传到服务器,最常见的就是图片上传了,大家都经历过上传身份证吧~
首先我们要搞定服务器
在项目的根目录下存在一个文件夹server
, 进入这个文件夹,打开终端
npm install 或 cnpm install
node index.js
之后存在上传图片的接口地址:http://localhost:3000/api/upload
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
url | string | 是 | 开发者服务器地址 | |
filePath | string | 是 | 要上传文件资源的路径 (本地路径) | |
name | string | 是 | 文件对应的 key,开发者在服务端可以通过这个 key 获取文件的二进制内容 | |
formData | Object | 否 | HTTP 请求中其他额外的 form data | |
timeout | number | 否 | 超时时间,单位为毫秒 | |
success | function | 否 | 接口调用成功的回调函数 | |
fail | function | 否 | 接口调用失败的回调函数 | |
complete | function | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) |
/**
* 如果需要用真机测试,需要注意的是:
* 1.手机和电脑必须在同一个网络下
* 2.要将localhost更换成本机ip,在控制台中通过ipconfig获取本机ip
* 3.这样就可以实现交互了
* */
Page({
uploadFile(){
wx.chooseImage({
success(res){
const tempFilePaths = res.tempFilePaths
wx.uploadFile({
filePath: tempFilePaths[0],
name: 'file',
url: 'http://localhost:3000/api/upload',
formData:{
"user":"test"
},
timeout:10000,
success(res){
const data = res.data
console.log(data);
},
fail(msg){
console.log(msg);
},
complete(){
console.log("完成");
}
})
}
})
}
})
如果需要用真机测试,需要注意的是:
* 1.手机和电脑必须在同一个网络下
* 2.要将localhost更换成本机ip,在控制台中通过ipconfig获取本机ip
* 3.这样就可以实现交互了
在开发过程中,有些需求是数据需要持久保存在程序中的,不随程序关闭而删除
例如:用户基本信息、主题颜色等
在微信小程序中,提供了对数据的存储操作:
wx.setStorage()
wx.getStorage()
wx.removeStorage()
wx.clearStorage()
将数据存储在本地缓存中指定的 key 中。会覆盖掉原来该 key 对应的内容。除非用户主动删除或因存储空间原因被系统清理,否则数据都一直可用。单个 key 允许存储的最大数据长度为 1MB,所有数据存储上限为 10MB
// pages/storage/storage.js
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// 设置缓存
wx.setStorage({
key: "name",
data: "hello",
// 开启AES加密
encrypt: true
})
},
})
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
key | string | 是 | 本地缓存中指定的 key | |
data | any | 是 | 需要存储的内容。只支持原生类型、Date、及能够通过JSON.stringify 序列化的对象。 |
|
encrypt | Boolean | false | 否 | 是否开启加密存储。只有异步的 setStorage 接口支持开启加密存储。开启后,将会对 data 使用 AES128 加密,接口回调耗时将会增加。若开启加密存储,setStorage 和 getStorage 需要同时声明 encrypt 的值为 true。此外,由于加密后的数据会比原始数据膨胀1.4倍,因此开启 encrypt 的情况下,单个 key 允许存储的最大数据长度为 0.7MB,所有数据存储上限为 7.1MB |
温馨提示
AES加密:高级加密标准(英语:Advanced Encryption Standard,缩写:AES) 是一种区块加密标准。AES可以使用128、192和256位密钥,从安全性来看,AES256安全性最高。从性能来看,AES128性能最高
例如:
加密前内容:百战程序员
加密后内容:U2FsdGVkX1/ws/a97KhEomtKuz3QXWf0QU9mcw1XTH0=
解密后内容:百战程序员
从本地缓存中异步获取指定 key 的内容
// pages/storage/storage.js
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// 获取缓存
wx.getStorage({
key: "name",
success(res) {
// 设置的时候开启AES加密,获取的时候也要开启加密
encrypt: true
console.log(res.data)
}
})
},
})
从本地缓存中移除指定 key
为了避免意外,我们最好用try...catch
进行捕获
// pages/storage/storage.js
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// try。。catch捕获错误
try {
// 移除某一项缓存
wx.removeStorage({
key: 'name',
success(res) {
console.log(res);
}
})
} catch (error) {
console.log(error);
}
},
})
清理本地数据缓存
// pages/storage/storage.js
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// 清空所有的缓存
wx.clearStorage()
},
})
// pages/storage/storage.js
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// 设置缓存
wx.setStorage({
key: "name",
data: "hello",
// 开启AES加密
encrypt: true
})
// 获取缓存
wx.getStorage({
key: "name",
success(res) {
// 设置的时候开启AES加密,获取的时候也要开启加密
encrypt: true
console.log(res.data)
}
})
// try。。catch捕获错误
try {
// 移除某一项缓存
wx.removeStorage({
key: 'name',
success(res) {
console.log(res);
}
})
} catch (error) {
console.log(error);
}
// 清空所有的缓存
wx.clearStorage()
},
})
数据缓存有两套操作方案,一套是异步操作,一套是同步操作
我们之前讲解的就是异步操作,而同步操作如下(只是在后面多了Sync
):
wx.setStorageSync()
wx.getStorageSync()
wx.removeStorageSync()
wx.clearStorageSync()
温馨提示
异步不会阻塞当前任务,同步缓存直到同步方法处理完才能继续往下执行
通俗的说:异步就是不管保没保存成功,程序都会继续往下执行.同步是等保存成功了,才会执行下面的代码
使用异步,性能会更好;而使用同步,数据会更安全
// pages/storageSync/storageSync.js
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
wx.setStorageSync('name', "loveyou")
var name = wx.getStorageSync('name')
console.log(name);
wx.removeStorageSync('name')
wx.clearStorageSync()
},
})
监听用户点击页面内转发按钮,可以发送给你的好友或者分享到你的朋友圈哦
分享给好友和分享到朋友圈是需要分别添加不同函数的
onShareAppMessage()
onShareTimeline()
字段 | 说明 | 默认值 |
---|---|---|
title | 转发标题 | 当前小程序名称 |
path | 转发路径 | 当前页面 path ,必须是以 / 开头的完整路径 |
imageUrl | 自定义图片路径,可以是本地文件路径、代码包文件路径或者网络图片路径。支持 PNG 及JPG。显示图片长宽比是 5:4。 | 使用默认截图 |
// pages/share/share.js
Page({
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
return {
title:"分享到好友",
url:"/pages/share/share",
imageUrl:"../../images/2023-02-23 082358(6).jpg",
menu:["shareAppMessage"]
}
}
})
字段 | 说明 | 默认值 |
---|---|---|
title | 自定义标题,即朋友圈列表页上显示的标题 | 当前小程序名称 |
query | 自定义页面路径中携带的参数,如 path?a=1&b=2 的 “?” 后面部分 | 当前页面路径携带的参数 |
imageUrl | 自定义图片路径,可以是本地文件或者网络图片。支持 PNG 及 JPG,显示图片长宽比是 1:1 | 默认使用小程序 Logo |
// pages/share/share.js
Page({
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
return {
title:"分享到好友",
url:"/pages/share/share",
imageUrl:"../../images/2023-02-23 082358(6).jpg",
menu:["shareAppMessage","shareTimeLine"]
}
},
onShareTimeline(){
return {
title:"分享到朋友圈",
query:"/pages/share/share",
imageUrl:"../../images/2023-02-23 082358(6).jpg"
}
}
})
获取用户信息。页面产生点击事件后才可调用,每次请求都会弹出授权窗口,用户同意后返回 userInfo
通过wx.getUserProfile()
方法进行获取
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
desc | string | 是 | 声明获取用户个人信息后的用途,不超过30个字符 | |
success | function | 否 | 接口调用成功的回调函数 | |
fail | function | 否 | 接口调用失败的回调函数 | |
complete | function | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) |
{{ userInfo.language}}
{{ userInfo.nickName}}
// pages/userinfo/userinfo.js
Page({
data(){
userInfo:""
},
bindUserInfo(){
wx.getUserProfile({
desc: '获取用户信息',
success:(res) =>{
console.log(res);
this.setData({
userInfo:res.userInfo
})
},
fail(error){
console.log(error);
},
complete(){
console.log("获取成功");
}
})
}
})
小程序可以通过微信官方提供的登录能力方便地获取微信提供的用户身份标识,快速建立小程序内的用户体系
温馨提示
- 会话密钥
session_key
是对用户数据进行 加密签名 的密钥。- 临时登录凭证 code 只能使用一次
- 调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。
- 调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 、 用户在微信开放平台帐号下的唯一标识UnionID(若当前小程序已绑定到微信开放平台帐号) 和 会话密钥 session_key
服务器端我们采用nodejs
来实现,需要提示大家,我们注重的是前端,所以服务器端代码不考虑优化加密问题,单纯是为了测试,在真实的开发场景中,服务器端代码会由服务器开发人员编写
index.js文件
// index.js文件 const express = require("express"); const app = express(); const router = require("./router"); const bodyParser = require("body-parser"); const cors = require("cors"); // 解决跨域 app.use(cors()); app.use(bodyParser.urlencoded({ extended:true })) app.use("/api",router); app.listen(3000,()=>{ console.log("服务器运行在3000端口上"); })
package.json文件
{ "dependencies": { "body-parser": "^1.20.0", "cors": "^2.8.5", "express": "^4.18.1", "request": "^2.88.2" } }
router.js文件
// router.js文件 const express = require("express"); const router = express.Router(); const request = require("request"); const authorization_code = "itbaizhan" const appid = "wx35ddcb7acea2d0c8" const secret = "30589f81bf69bc66485f4fd5fa03db4c" router.post("/login", (req, res) => { // 获取到登录后的code const { code} = req.body; // 向微信服务器发送信息获取到 openid 和 session_key // https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code request(`https://api.weixin.qq.com/sns/jscode2session?appid=${appid}&secret=${secret}&js_code=${code}&grant_type=${authorization_code}`, (err, response, body) => { if (err) console.log(err); const data = JSON.parse(body); /* 签名校验以及数据加解密涉及用户的会话密钥session_key。 需要保存在服务器 openid 判断是否是同一个用户 session_key 判断用户是否失效 data: { openid: '**********', session_key: '********' } */ res.send(data) }) }) module.exports = router;
注意:
这是本地测试的服务端口:(运行在本地3000的端口上)
运行前:需要在开发者工具的当前路径下运行以下代码:
npm install node index.js
这样就可以了运行在本地端口上了
服务器端实现之后,我们来写小程序端的代码
调用接口获取登录凭证(code)。通过凭证进而换取用户登录态信息,包括用户在当前小程序的唯一标识(openid)、微信开放平台帐号下的唯一标识(unionid,若当前小程序已绑定到微信开放平台帐号)及本次登录的会话密钥(session_key)等。用户数据的加解密通讯需要依赖会话密钥完成
// pages/login/login.js
Page({
/**
* 页面的初始数据
*/
data: {
},
bindLogin(){
// 微信api提供的登录获取code的方案
wx.login({
success: (res) => {
console.log("code",res.code)
wx.request({
url: 'http://localhost:3000/api/login',
method:"POST",
data:{
code:res.code
},
header:{
'Content-Type': 'application/x-www-form-urlencoded'
},
success (result) {
console.log(result.data)
},
fail (error) {
console.log(error);
}
})
},
fail:(err) =>{
console.log(err)
}
})
}
})
这样就可以拿到微信服务端传给我们的session-key和openid
注意:
如果出现以下错误
原因:
1.检查后端的AppID和AppSecret是否正确
2.检查data:{code:res.code},是否传递正确
开发者可以将页面内的功能模块抽象成自定义组件,以便在不同的页面中重复使用;也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护。自定义组件在使用时与基础组件非常相似
类似于页面,一个自定义组件由 json
wxml
wxss
js
4个文件组成。要编写一个自定义组件,首先需要在 json
文件中进行自定义组件声明(将 component
字段设为 true
可将这一组文件设为自定义组件)
{
"component": true
}
使用已注册的自定义组件前,首先要在页面的 json
文件中进行引用声明。此时需要提供每个自定义组件的标签名和对应的自定义组件文件路径
{
"usingComponents": {
"component-tag-name": "../../components/counter/counter"
}
}
之后,将组件名字在.wxml
文件中引入即可
在组件内部,可以直接实现组件的业务逻辑,主要有以下几点:
properties
data
methods
我是自定义组件
{{ title }}
{{ text }}
Component({
properties: {
title: {
type: String,
value: 'default value',
}
},
data: {
text:"测试数据"
},
methods: {
clickHandle(){
console.log("点击了");
}
}
})
在组件模板中可以提供一个
节点,用于承载页面引用时提供的子节点
这种方式与直接传递数据是有区别的,他是可以传递视图的!
{{ item }}
// components/list/list.js
Component({
properties: {
listData:{
type:Array,
value:[]
}
}
})
.title{
margin: 5px;
}
温馨提示
在组件中,样式只允许使用
class
定义
{
"usingComponents": {
"list":"../../components/list/list"
}
}
{{ userTitle }}
{{ dataTitle }}
Page({
data: {
userList:["iwen","ime","frank"],
userTitle:"用户列表",
dataList:["前端","python","Java"],
dataTitle:"课程列表"
}
})
了解了小程序组件之后,我们实现一个自定义组件Dialog
,也就是弹出框
{{title}}
{{content}}
{{cancelText}}
{{confirmText}}
// components/dialog/dialog.js
Component({
/**
* 组件的属性列表
*/
properties: {
title:{
type:String,
value:"标题"
},
content:{
type:String,
value:"删除有风险,谨慎操作"
},
cancelText:{
type:String,
value:"取消"
},
confirmText:{
type:String,
value:"确定"
}
},
/**
* 组件的初始数据
*/
data: {
isshow:false
},
/**
* 组件的方法列表
*/
methods: {
hideDialog(){
this.setData({
isshow:!this.data.isshow
})
},
showDialog(){
this.setData({
isshow:!this.data.isshow
})
},
// _cancelEvent和_confirmEvent是内部私有方法
/**
* 内部私有方法需要下划线开头
* triggerEvent 用于触发事件
*/
_cancelEvent(){
// 把取消按钮事件发送到页面中去处理
this.triggerEvent("cancelEvent")
},
_confirmEvent(){
// 把确定按钮事件发送到页面中去处理
this.triggerEvent("confirmEvent")
},
}
})
.wx-mask {
position: fixed;
z-index: 1000;
top: 0;
right: 0;
left: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.3);
}
.wx-dialog {
position: fixed;
z-index: 5000;
width: 80%;
max-width: 600rpx;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #FFFFFF;
text-align: center;
border-radius: 3px;
overflow: hidden;
}
.wx-dialog-title {
font-size: 18px;
padding: 15px 15px 5px;
}
.wx-dialog-content {
padding: 15px 15px 5px;
min-height: 40px;
font-size: 16px;
line-height: 1.3;
word-wrap: break-word;
word-break: break-all;
color: #999999;
}
.wx-dialog-footer {
display: flex;
align-items: center;
position: relative;
line-height: 45px;
font-size: 17px;
}
.wx-dialog-footer::before {
content: '';
position: absolute;
left: 0;
top: 0;
right: 0;
height: 1px;
border-top: 1px solid #D5D5D6;
color: #D5D5D6;
transform-origin: 0 0;
transform: scaleY(0.5);
}
.wx-dialog-btn {
display: block;
flex: 1;
position: relative;
}
.wx-dialog-footer .wx-dialog-btn:nth-of-type(1) {
color: #353535;
}
.wx-dialog-footer .wx-dialog-btn:nth-of-type(2) {
color: #3CC51F;
}
.wx-dialog-footer .wx-dialog-btn:nth-of-type(2):after {
content: " ";
position: absolute;
left: 0;
top: 0;
width: 1px;
bottom: 0;
border-left: 1px solid #D5D5D6;
color: #D5D5D6;
transform-origin: 0 0;
transform: scaleX(0.5);
}
{
"usingComponents": {
"dialog":"../../components/dialog/dialog"
}
}
// pages/home/home.js
Page({
/**
* 页面的初始数据
*/
data: {
title:"标题",
content:"删除有风险,谨慎操作",
cancelText:"残忍离开",
confirmText:"欣然接受",
dialog:null
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// 页面中读取组件对象
this.dialog = this.selectComponent("#myDialog")
},
bindDialog(){
this.dialog.showDialog()
},
cancelEvent(){
console.log("cancel");
this.dialog.hideDialog()
},
confirmEvent(){
console.log("confirm");
this.dialog.hideDialog()
}
})
TDesign 企业级设计体系(前端UI组件库)
地址:https://tdesign.tencent.comhttps://tdesign.tencent.com/
支持桌面端、移动端、小程序
温馨提示
目前组件库处于 Beta 阶段,快速迭代中,请留意版本变化
使用 NPM
小程序已经支持使用 NPM 安装第三方包
npm i tdesign-miniprogram -S --production
// 安装完之后,需要在微信开发者工具中对 npm 进行构建:工具 - 构建 npm
修改 app.json
将 app.json
中的 "style": "v2"
移除
因为该配置表示启用新版组件样式,将会导致 TDesign 的组件样式错乱
使用组件
以按钮组件为例,只需要在 JSON
文件中引入按钮对应的自定义组件即可
{
"usingComponents": {
"t-button": "tdesign-miniprogram/button/button"
}
}
接着就可以在 wxml 中直接使用组件
按钮
Vant 是一个轻量、可靠的移动端组件库
地址:Vant 4 - A lightweight, customizable Vue UI library for mobile web apps.
步骤一 通过 npm 安装
npm i @vant/weapp -S --production
步骤二 修改 app.json
将 app.json 中的 "style": "v2"
去除,小程序的新版基础组件强行加上了许多样式,难以覆盖,不关闭将造成部分组件样式混乱。
步骤三 修改 project.config.json
开发者工具创建的项目,miniprogramRoot
默认为 miniprogram
,package.json
在其外部,npm 构建无法正常工作。
需要手动在 project.config.json
内添加如下配置,使开发者工具可以正确索引到 npm 依赖的位置
{
...
"setting": {
...
"packNpmManually": true,
"packNpmRelationList": [
{
"packageJsonPath": "./package.json",
"miniprogramNpmDistDir": "./"
}
]
}
}
温馨提示
由于目前新版开发者工具创建的小程序目录文件结构问题,npm构建的文件目录为miniprogram_npm,并且开发工具会默认在当前目录下创建miniprogram_npm的文件名,所以新版本的miniprogramNpmDistDir配置为'./'即可
步骤四 构建 npm 包
打开微信开发者工具,点击 工具 -> 构建 npm,并勾选 使用 npm 模块 选项,构建完成后,即可引入组件
按钮用于触发一个操作,如提交表单
在app.json
或index.json
中引入组件
"usingComponents": {
"van-button": "@vant/weapp/button/index"
}
代码演示
支持default
、primary
、info
、warning
、danger
五种类型,默认为default
。
默认按钮
主要按钮
信息按钮
警告按钮
危险按钮