微信小程序
注册小程序帐号
微信公众平台->注册->小程序(留一个微信号,作为管理员,留一个邮箱作为登录用)
安装开发、测试工具
开发工具使用
创建项目,开发,调试,打包,部署,git
开发文档
获取开发秘钥
公众平台->登录小程序->开发->开发设置->AppID(小程序ID) wxcb2ef166e0715ba9 ->秘钥:a428535fc115b52f2d60323b069a75e5
框架
微信客户端给小程序所提供的环境为宿主环境,小程序的运行环境分成渲染层(webview)和逻辑层(jscore),WXML 模板和 WXSS 样式工作在渲染层,JS 脚本工作在逻辑层,小程序的渲染层和逻辑层分别由2个线程管理,这两个线程的通信会经由微信客户端
数据绑定
响应式数据定义在 data:{} ,wxml数据绑定,格式{{ 数据 }} | 属性="{{值}}"
事件
<组件 bindxxx="实例方法"></组件> 冒泡
xxx==原生移动端事件名(touchstart/touchend/touchcancel/touchmove/tap/....)
<组件 catchxxx="实例方法"></组件> 不冒泡
传参
<组件 bindxxx="实例方法" data-参数名称="值"></组件>
值: 字符
<组件 bindxxx="实例方法" data-参数名称="{{any}}"></组件>
any 任意类型
实例方法:function(e){e.currentTarget.dataset.参数名称;}
e返回事件对象
数据修改
data:{} 数据,在修改实例属性,数据的修改结果是异步
this.data.属性 = 值 修改, model变化,view层不实时响应
this.setData({key:value}) 修改,催生view层响应
this指向页面实例
列表渲染
<组件 wx:for="{{数据}}">{{item}}/{{index}}</组件>
wx:key="id" 定key id = item.id
wx:key="key" 定key key = item.key
wx:key="*this" 定key *this = item 本身
wx:for-item="xx" 定义item的名字->xx
wx:for-index="xx" 定义index的名字->xx
条件渲染
<组件 wx:if="{{布尔数据}}"> 惰性渲染 ~~ v-if
wx:elif="{{}}"
wx:else
<组件 hidden="{{布尔数据}}" 适合频繁渲染 ~~ v-show
不渲染
分组<block wx:if="">被包裹的元素</block>,或声明业务逻辑,自身不渲染
双向绑定
<input value="{{ipt}}" bindinput="checkIpt"></input>
checkIpt(e){
this.setData({ipt:e.detail.value});//双向绑定
},
简易双向绑定机制。此时,可以在对应项目之前加入 model: 前缀:
<input model:value="{{value}}" />
注册小程序、页面
App() 注册小程序, Page() 注册页面的,都接受一个 Object 参数,App() 必须在 app.js 中调用,Page()必须出现在页面.js中,必须调用且只能调用一次,Object 的key,都会是当前实例成员(方法、属性)
Object 参数
- data:{} 数据
- 钩子函数(参数){this 指向当前页面,当前小程序}
- 自定义函数(){ this 指向当前页面,当前小程序 }
- 自定义属性:值
页面与主程通讯
pages里面 let app=getApp(), app.实例属性|方法
App里面 let pages = getCurrentPages(),pages[index].实例属性|方法
生命周期
app
小程序初始化onLaunch(传递给当前小程序的参数)
切到前台onShow|后台onHide
pages
初始化onLoad(路由传递过来的参数和数据)
切前后台/第一次渲染完毕onReady
卸载前onUnload
转发/下拉/触底/滚动
模块化
支持commonJs / es Modules
es6+
默认支持es6语法
wxss
WXML就是组件,非DOM标签,WXSS就是简版的css,提供rpx响应式布局,IPHONE6为基准,采用双倍布局(1px~~2rpx),目前支持的选择器有
| 选择器 | 样例 | 样例描述 |
|---|---|---|
| .class | .intro | 选择所有拥有 class="intro" 的组件 |
| #id | #firstname | 选择拥有 id="firstname" 的组件 |
| element | view | 选择所有 view 组件 |
| element, element | view, checkbox | 选择所有文档的 view 组件和所有的 checkbox 组件 |
| ::after | view::after | 在 view 组件后边插入内容 |
| ::before | view::before | 在 view 组件前边插入内容 |
| element1 element2 |
注意:自定义组件内部不推荐id,element选择器
移动端适配: 750rpx标准设计稿+flex布局+类选择器+rpx单位
配置
为了方便开发者减少配置项,描述页面的四个文件必须具有相同的路径与文件名
全局配置
app.json,对微信小程序进行全局配置(页面管理,窗口设置,网络请求)
页面配置
pagename.json,对本页面的窗口表现进行配置,覆盖全局
项目环境配置
sitemap.json,project.config.json ,sitemap.json 文件用于配置小程序及其页面是否允许被微信索引(SEO),需要全局禁用索引,配置project.config.json, setting内部添加checkSiteMap:false, project.config.json是项目开发环境配置
组件
内置组件
声明式路由
组件:navigator
属性: open-type
值:
-
navigate,redirect 只能打开非 tabBar 页面
-
switchTab 只能打开 tabBar 页面。
-
reLaunch 可以打开任意页面
-
navigate 新+页面栈
-
redirect 会替换当前栈
-
navigateBack 当前页面出栈
-
switchTab 目标tabBar页面入栈 关闭其他所有非 tabBar 页面
-
reLaunch 全部出栈,目标页面栈入栈
-
exit 全部出栈,无进栈 target="miniProgram"时生效
路由传参
url="/pages/xx/xx?a=1&b=2" switchTab 不能传参数
接参
app.js onLaunch(options) options={a:1,b:2}
pages.js onLoad(options) options={a:1,b:2}
跳转其他小程序
条件:需要设置对方小程序的appId,在我方小程序的app.json,不可以跳转自己的appId
// app.json 对方的appId 基础库 2.12.1+ 后无需设置
{
"navigateToMiniProgramAppIdList":[
"wxcb2ef166e0715ba9"
]
}
<navigator
target="miniProgram"
open-type="navigate" //不替换当前小程序
app-id="wxcb2ef166e0715ba9" //对方的appId
extra-data="{a:1,b:2}" //传给对方小程序的目标页面的数据
path="/pages/dongbula/dongbula" //要打开的对方的小程序页面
>跳转到其他小程序</navigator>
自定义组件
定义
不是创建page,而是创建component,一个小程序组件(wxml,wxss,js,json)
Component({
properties: {
myProperty: { // 属性名
type: String,
value: ''
},
myProperty2: String // 简化的定义方式
},
data: {}, // 私有数据,可用于模板渲染
lifetimes: {
// 生命周期函数,可以为函数,或一个在 methods 段中定义的方法名
created: function () { },//还不能调用 setData
attached: function () {
this.triggerEvent('heheda', {a:1,b:2});//触发页面函数传参数 <xx bindheheda="页面函数"
},//进入页面节点树后
detached: function () { },//在组件离开页面节点树后
},
pageLifetimes: {
// 组件所在页面的生命周期函数
show: function () { },//组件所在的页面被展示时
hide: function () { },
resize: function () { },//组件所在的页面尺寸变化时执行
},
methods: {
onMyButtonTap: function(){
this.setData({
// 更新属性和数据的方法与更新页面数据的方法类似
})
}
}
})
注册
全局注册 app.json , 到处可用(page,component)
"usingComponents":{
"使用时的组件名":"components/comp1/comp1"
}
局部注册 pagename.json ,当前页面可以
"usingComponents":{....}
组件内注册组件, 当前组件可用
"usingComponents":{....}
使用
<comp1 title="{{值}}" bindxxx="页面函数"></comp1>
组件内部样式是局部的,与外界隔离(scoped),外部传入的样式不会覆盖内部样式
默认样式
app.wxss中的样式、页面的的样式对自定义组件无效- 自定义组件样式,只对组件 wxml 内的节点生效,不影响其他组件和页面
app.wxss中不规则使用了元素选择器,会影响到自定义组件
接受外部样式
Component({
options: {
addGlobalClass: true,//仅接受全局、页面样式、不会反向影响其他页面和组件
}
})
默认组件内部样式权重高于外部,外部需要修改组件内部的样式时
/*pagesname.wxss*/ .覆盖组件的选择器{ 非同名属性:值; 同属性:值 !important; }
第三方组件
下载单一组件
npm init -y 执行一次
npm i miniprogram-组件名 --production 安装到node_modules里面,不可以引入
npm i miniprogram-组件名2 --production 安装到node_modules里面,不可以引入
小程序开发工具-构建npm -> miniprograme_npm
小程序会指向这个miniprograme_npm目录,二不是node_modules
可以引入无需路径
{
"usingComponents": {
"miniprogram-picker": "miniprogram-picker"
}
}
一定要是miniprograme_npm名称么,可否自定义?
miniprograme_npm是自动产生,可以定义
定义xxx目录: 无需执行小程序开发工具-构建npm, 手动从node_modules里面copy到xxx目录
可以引入需要路径
{
"usingComponents": {
"miniprogram-picker": "components/miniprogram-picker"
}
}
使用weui库
pc端: elementUi / iview / antd
移动端: vant / mintUi / ameizi / antd-m
小程序端 weui|weapp: wevant / weiview / 官方扩展 特点:不操作dom
将 app.json 中的
"style": "v2"去除,小程序的新版基础组件强行加上了许多样式,难以去除,不关闭将造成部分组件样式混乱。
添加新样式: vant custom-class="类名" iview i-class="类名",定义组件跟节点样式,追加一些属性到组件的跟节点上,无法替换已有的属性
修改样式顺序: 定义主题->传递组件属性->custom-class="类名"->
<van-button type="primary" custom-class="vant-button-root">主要按钮</van-button>
.vant-button-root{
border-radius: 40rpx !important;
font-size: 20px !important;
background-color: pink !important;
}
/*子节点*/
.vant-button-root .van-button__text{
color: red !important;
}
API
系统
getSystemInfoSync
编程式路由
界面
交互、导航栏、下拉
网络
读取本地数据? 可以, 需要打开本地设置-》不校验合法域名
软件、app、小程序 数据交互不存在跨域的,因为宿主不是浏览器
媒体
图片、视频、位置
数据缓存
原生项目
自定义头部导航栏开发
- 手机状态栏:不同手机高度不同,需要api支持
- 大多数机型,小程序头部导航栏的高度是 40rpx (齐刘海)88rpx
//xx.json
{
"navigationStyle": "custom" //关闭导航
}
api获取当前设备的状态栏高度:
//同步获取
const result = wx.getSystemInfoSync()
result.statusBarHeight //状态栏的高度 px
获取节点渲染后的实际宽高
const query = wx.createSelectorQuery()
query.select('#header').boundingClientRect()
query.exec(res=>{
res[0].height
})
自动拨号功能
wx.makePhoneCall({
phoneNumber: '电话号码',
success:()=>{}, //可选:拨号成功
fail:()=>{}, //可选:异常
complate:()=>{} //可选:始终执行
})
插件应用
前置条件
- 腾讯位置服务注册开发账号
- 登录【控制台】【应用管理】【我的应用】【创建应用】
- 【添加key】全选启用产品,填入自己的appID,得到秘钥(CC4BZ-O5J6X-LQE4Y-TPQKA-PKLBH-ZZBTJ)
得到插件有哪些方式
- npm安装到项目使用
- 使用微信服务市场的在线插件
**需求1:**提供地址,转换经纬度,并绘制到地图
//下载 腾讯地址辅助插件 复制到当前的小程序目录下 utils/
npm install qqmap-wx-jssdk --save
实现:
const QQMap = require('../../utils/qqmap-wx-jssdk.min');
const qqmap = new QQMap({
key: 'CC4BZ-O5J6X-LQE4Y-TPQKA-PKLBH-ZZBTJ',//前置条件创建的秘钥
})
qqmap.geocoder({
address: "徐汇区桂林路81号(上师大东校区)文苑楼2楼",
success: res=>{
console.log('qqmap res',res)
}
})
需求2:提供地址经纬度,规划出路线,并绘制到地图
实现:微信服务市场 搜索【[腾讯位置服务】选择 【腾讯位置服务路线规划】【添加插件】选择你的小程序,之后按照【接入文档】操作
用户登录login
小程序的登录无需输入用户信息,因为通过微信进入小程序,是合法微信用户就是合法小程序用户,我们自身数据库存了用户的信息(订单),也合并了微信服务器的用户信息(头像),要抓取这些数据需要如下步骤
- 验证合法登录微信后进入小程序:wx.login获取code,验证通过微信进入小程序,避免身份伪装
- 抓取微信服务器用户部分数据作为访问自身服务器的关键参数:请求微信官方接口(携带code,appId,secret),获取微信用户信息(openid,session_key,union_id)
- 请求mongodb获取用户信息,存入globalData全局使用
正常登录只需要与自己的服务器互动,但进入小程序的用户,他属于微信用户且小程序运行在微信基础上,如果需要自己的mongodb用户数据(订单),需要向微信服务器做出1、2步骤的请求
//app.js
async onLaunch(){
//登录 得到code ==>请求微信接口==》换取openid session_key ==> 向mongodb获取用户信息
const {code} = await wx.login()
const options = {
//小程序appId
appid: 'wx4db1d3b76a677753',
//小程序密钥
secret: '9fc4d4c069bb2cc31bda622b6a52d4b0',
//获取到的code
js_code: code,
//操作类型
grant_type: 'authorization_code',
//连接类型
connect_redirect: 1
}
// 请求微信接口
const {data:{openid}} = await request({
url: 'https://api.weixin.qq.com/sns/jscode2session',
method: 'get',
data: options
})
//请求mongodb
const {data} = await getUser(openid)
this.globalData.userInfo = data//存入全局
}
用户授权
工信部要求,用户许可,并主动触发,才可获取隐私信息(头像,昵称),对应场景如下
- 【我的】需要获取用户信息
- 找globalData抓hasAuthorize,如果没有授权,跳转【授权】页面
- 【授权】页面给用户提供主动触发的按钮,用户点击后获取用户信息(getUserProfile)
- 写入全局,写入mongodb数据库,跳转回【我的】
//用户授权方法,必须在自定义方法中才有效
//wx.getUserInfo()方法已经作废,始终返回匿名用户信息-空的
const {userInfo} = await wx.getUserProfile({
desc: '我同意获取头像、昵称等信息'
})
//获取到的结果中,相同的数据,有三套
//encryptedData:加密数据:需要使用相关信息解密
//rawData: 字符串数据
//userInfo: json格式数据
//准备需要入库 数据
let user = {...app.globalData.userInfo,...userInfo,hasAuthorize:true,authorizeTime:moment().format('YYYY-MM-DD HH:mm:ss')}
//更新全局数据对象
app.globalData.userInfo = user
//更新自己的数据库
const {data} = await updateUser(user)
//返回我的页面
data===1 && wx.switchTab({
url: '/pages/mine/mine',
})
获取手机号
手机号码属于"隐私信息",从21年开始,手机号码获取的操作,已经禁止直接获取了, 其次,必须是企业版小程序才可以。个人版小程序方法无效的。
<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">获取手机</button>
getPhoneNumber(e) {
console.log('mine',e.detial);//企业版小程序才会有数据
//获取到后发送到node服务器入库到user集合
//个人小程序方案
//用户input输入号码,提交
//请求接口,ajax操作,服务器入库
},
=======扩展选学=======
云开发
初始云环境
- 开发工具->创建云开发项目(选择云开发)
- 必须填入appID
- 开发工具->云开发->设置->创建环境->输入环境名称
- 开发工具->右键点击cloudfunctions目录,切换你创建的环境
- 右键点击cloudfunctions/login云函数->上传并部署 (为了将来的小程序可以访问你的云环境)
非云开发环境改装成云开发环境
修改project.config.json 添加云函数目录
"cloudfunctionRoot": "cloudfunctions/",工作区创建cloudfunctions目录
环境切换到对应环境
生成login云函数(手写,或者同步已有云函数)
app.js 指定环境env
多云环境
初始云环境的动作,做多次,注意:目前免费环境支持两个,多了没有,一般做一个测试和一个正式环境
多环境情况下需要指定env
// app.js
onLaunch: function () {
if (!wx.cloud) {
console.error('请使用 2.2.3 或以上的基础库以使用云能力')
} else {
wx.cloud.init({
// env 参数说明:
// env 参数决定接下来小程序发起的云开发调用(wx.cloud.xxx)会默认请求到哪个云环境的资源
// 此处请填入环境 ID, 环境 ID 可打开云控制台查看
// 如不填则使用默认环境(第一个创建的环境)
env: 'test-vpu1v', // 环境id
traceUser: true,
})
}
}
数据库环境
创建集合
开发工具->云开发->数据库->创建集合->权限设置(最大)
创建者(后端),所有用户(客户端)
添加记录
手动添加: 开发工具->云开发->数据库->添加记录
导入:本地mongodb数据、导入第三方的数据,要求数据是json(出库)
本地mongodb出库: mongoexport -h 127.0.0.1 -d 库名 -c 集合名 -o 输出路径/xx.json
//xxx.json 格式 {}{}{}{}
//导入的数据,如果需要客户端,可以写入的权限,需要自定义权限 | 给数据添加_openid
获取openid
//获取openId
let {result} = await wx.cloud.callFunction({
name: 'login'
})
console.log('openid', result.openid)
const wxContext = wx.cloud.getWXContext();
数据库操作
链接库
const db = wx.cloud.database()
增
db.collection('bulala')
.add({ //增
data: { //一条
name: 'apple',
category: 'fruit',
price: 10
}
})
.then(
res=>console.log('res111111',res)
)
.catch(
err=>console.log('err111111',err)
)
删
db.collection('bulala')
.doc('3f8c212f5ea1086f00008dc55c74c585') //冲着_id
.remove()
.then()
.catch()
//_openid
改
db.collection('test')
.doc(_id)
//.set({ // 替换更新
.update({ // 局部更新
data: {
name: 'milk',
category: 'dairy',
price: 18,
}
})
查
db.collection('bulala')
.where({ //查询条件
price: _.gt(10)
})
.field({//允许返回的字段
name: true,
price: true,
})
.orderBy('price', 'desc')//按关键词排序
.skip(1)
.limit(10)
.get() //获取
批量操作(批量添加/删除),不可以在客户端完成,需要在服务端(云函数)里面完成
数据推送
A页面修改了集合,B页面事先监听了这个集合,就会收到更新后的数据,这个数据是后端推送出来的(广播)
//客户端监听(订阅)
watcherId = 集合.where({监听条件}).watch({onChange:fn,onError:fn})
//客户端关闭监听
watcherId.close()
//服务端推送(发布) 有微信的服务器完成
场景:聊天室,web微信,新消息推送
上传图片
wx.chooseImage({
...
success: (res) => {
const filePath = res.tempFilePaths[0]//手机缓存
// 上传图片
const cloudPath = 'my-image' + filePath.match(/\.[^.]+?$/)[0]
wx.cloud.uploadFile({
cloudPath,//云端地址
filePath,//手机缓存地址
success: res => {
// console.log(res.fileID)//传递完后的云地址
//地址地段,和用户信息,入库(update)
},
fail: e => {
console.error('[上传文件] 失败:', e)
},
complete: () => {
}
})
},
fail: e => {
console.error(e)
}
})
云函数
创建
右键cloudfunctions->新建node云函数->定义函数名->右键函数名->上传并部署
编写
// 云函数入口文件
const cloud = require('wx-server-sdk')
//没有wx
cloud.init({
env: 'test-vpu1v',
})
// 云函数入口函数
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()//微信 上下文信息
//业务
cloud.database().collection('bulala').批量操作
//返回值
return {
event,
openid: wxContext.OPENID,
appid: wxContext.APPID,
unionid: wxContext.UNIONID,
}
}
测试调用
服务端测: 控制台->云端测试
客户测试: wx.cloud.callFunction({name: '函数名',data:{数据}}).then(结果)
云项目
方案1: 云开发模板环境
方案2:非云开发环境改装成云开发环境
修改project.config.json 添加云函数目录
"cloudfunctionRoot": "cloudfunctions/",工作区创建cloudfunctions目录
环境切换到对应环境
生成login云函数(手写,或者同步已有云函数)
app.js 指定环境env