微信小程序

219 阅读14分钟

微信小程序

注册小程序帐号

微信公众平台->注册->小程序(留一个微信号,作为管理员,留一个邮箱作为登录用)

安装开发、测试工具

下载

开发工具使用

创建项目,开发,调试,打包,部署,git

开发文档

框架 组件 API

获取开发秘钥

公众平台->登录小程序->开发->开发设置->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" 的组件
elementview选择所有 view 组件
element, elementview, checkbox选择所有文档的 view 组件和所有的 checkbox 组件
::afterview::after在 view 组件后边插入内容
::beforeview::before在 view 组件前边插入内容
element1 element2

注意:自定义组件内部不推荐id,element选择器

移动端适配: 750rpx标准设计稿+flex布局+类选择器+rpx单位

配置

为了方便开发者减少配置项,描述页面的四个文件必须具有相同的路径与文件名

全局配置

app.json,对微信小程序进行全局配置(页面管理,窗口设置,网络请求)

页面配置

pagename.json,对本页面的窗口表现进行配置,覆盖全局

项目环境配置

sitemap.jsonproject.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

weVantweIview

将 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)

得到插件有哪些方式

  1. npm安装到项目使用
  2. 使用微信服务市场的在线插件

**需求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

小程序的登录无需输入用户信息,因为通过微信进入小程序,是合法微信用户就是合法小程序用户,我们自身数据库存了用户的信息(订单),也合并了微信服务器的用户信息(头像),要抓取这些数据需要如下步骤

  1. 验证合法登录微信后进入小程序:wx.login获取code,验证通过微信进入小程序,避免身份伪装
  2. 抓取微信服务器用户部分数据作为访问自身服务器的关键参数:请求微信官方接口(携带code,appId,secret),获取微信用户信息(openid,session_key,union_id)
  3. 请求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//存入全局
  }

用户授权

工信部要求,用户许可,并主动触发,才可获取隐私信息(头像,昵称),对应场景如下

  1. 【我的】需要获取用户信息
  2. 找globalData抓hasAuthorize,如果没有授权,跳转【授权】页面
  3. 【授权】页面给用户提供主动触发的按钮,用户点击后获取用户信息(getUserProfile)
  4. 写入全局,写入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