微信原生小程序开发

11/1/2020 微信原生小程序开发

# 微信小程序简述

微信 (opens new window)小程序,简称小程序 (opens new window),英文名Mini Program,是一种不需要下载安装即可使用的[应用]

# 小程序概念

小程序没有DOM对象,一切基于组件化

小程序的四个重要的文件

*.wxml ---> view结构 ----> html
*.wxss ---> view样式 -----> css

# 小程序适配

Iphon6: 1rpx = 1物理像素 = 0.5px

微信官方提供的换算方式:

1.以iPhone6的物理像素个数为标准: 750;
2.1rpx = 目标设备宽度 / 750 * px;
3.注意此时底层已经做了viewport适配的处理,即实现了理想视	口

# 小程序工具构建目录架构

-pages				主要编写页面目录
-utils	  		工具目录
-app.js   		注册小程序js
-app.json			小程序全局配置
-app.wxss 		基本样式
-probject.config.json		项目配置文件
sitemap.json		迷

# app.json配置

{
  "pages": [
    "pages/index/main"
  ],
  "window": {
    "backgroundTextStyle": "light",
    "navigationBarBackgroundColor": "#fff",
    "navigationBarTitleText": "WeChat",
    "navigationBarTextStyle": "black"
  },
  "tabBar": {
    "color": "#999",
    "backgroundColor": "#fafafa",
    "selectedColor": "#333",
    "borderStyle": "white",

    "list": [{
      "text": "首页",
      "pagePath": "pages/index/main",
      "iconPath": "static/tabs/home.png",
      "selectedIconPath": "static/tabs/home-active.png"
    }, {
      "text": "订单",
      "pagePath": "pages/logs/main",
      "iconPath": "static/tabs/orders.png",
      "selectedIconPath": "static/tabs/orders-active.png"
    }],

    "items": [{
      "name": "首页",
      "pagePath": "pages/index/main",
      "icon": "static/tabs/home.png",
      "activeIcon": "static/tabs/home-active.png"
    }, {
      "name": "订单",
      "pagePath": "pages/logs/main",
      "icon": "static/tabs/orders.png",
      "activeIcon": "static/tabs/orders-active.png"
    }],
    "position": "bottom"
  }
}

# 小程序标签组件

# 基本组件

<view><!-- 块级元素 --></view>
<image src=""><!-- 图片元素 --></image>
<text><!-- 行内元素 --></text>
<web-view><!-- 承载网页, 自动铺满屏幕 --></web-view>
<rich-text nodes="{{info.content}}"><!-- 富文本组件, 渲染nodes中的html字符串 --></rich-text>

# 表单组件

<button><!-- 按钮 --></button>
<switch><!-- 开关 --></switch>
<slider><!-- 滑动进度条 --></slider>
<picker><!-- 滚动选择器 --></picker>
<checkbox-group>
  <!-- 多项选择容器 -->
	<checkbox><!-- 多项项目 --></checkbox>
</checkbox-group>
<radio-group>
  <!-- 单项选择容器 -->
	<radio><!-- 单项项目 --></radio>
</radio-group>

# 媒体组件

<camera><!-- 系统相机 --></camera>
<live-player><!-- 音视频直播 --></live-player>
<live-pusher><!-- 音视频录制 --></live-pusher>
<video><!-- 音视频播放 --></video>
<map><!-- 地图 --></map>
<canvas><!-- 画布 --></canvas>

# 开放组件

<ad><!-- 广告 --></ad>
<official-account><!-- 公众号关注 --></official-account>

# 小程序基本语法API

# 事件绑定

<!-- bind冒泡事件 -->
<view bind:tap="myEventCallBack">点击</view>
<!-- catch阻止冒泡事件 -->
<view catch:tap="myEventCallBack">点击</view>
<!-- mut-bind阻止冒泡事件 -->
<view mut-bind:tap="myEventCallBack">点击</view>
<!-- 滑动事件, 具有滑动值 -->
<view bindscroll="scroll"></view>
Page({
  myEventCallBack(ev){
    console.log(ev)
  }
})

事件传参

<view bind:tap="myEventCallBack"  data-id='{{item.id}}'>点击</view>
Page({
  myEventCallBack(ev){
    // 点击当前整体元素
    const id = ev.currentTarget.dataset.id
  }
})

事件委派

<view bind:tap="myEventCallBack">
	<view data-id="0">子元素A</view>
</view>
Page({
  myEventCallBack(ev){
    console.log(ev.target.dataset.id)
  }
})

# 大括号表达式

<view> {{ message }} </view>
<image src="{{url}}"/>
<view style="display:{{fool?'block':'none'}}"></view>

# 列表渲染

<view wx:for="{{array}}" wx:for-index="idx">
  {{index}}---{{item.message}}---{{idx}}
</view>

# 条件渲染

<view wx:if="{{length > 5}}"> 1 </view>
<view wx:elif="{{length > 2}}"> 2 </view>
<view wx:else> 3 </view>

# 获取标签元素的样式

//创建节点选择器
    const query = wx.createSelectorQuery()
    query.select('.fixed_box').boundingClientRect()
    query.selectViewport().scrollOffset()
    query.exec(function (res) {
      res[0].top       // #the-id节点的上边界坐标
      res[1].scrollTop // 显示区域的竖直滚动位置
      console.log('打印demo的元素的信息', res);
      console.log('打印高度', res[0].height);
    })

# 生命周期

// 100-小程序基本编写/pages/list/list.js
Page({

	/* 页面的初始数据 */
	data: {
	},

	/**
	 * 生命周期函数--监听页面加载
	 */
	onLoad: function (options) {

	},

	/**
	 * 生命周期函数--监听页面初次渲染完成
	 */
	onReady: function () {

	},

	/**
	 * 生命周期函数--监听页面显示
	 */
	onShow: function () {

	},

	/**
	 * 生命周期函数--监听页面隐藏
	 */
	onHide: function () {

	},

	/**
	 * 生命周期函数--监听页面卸载
	 */
	onUnload: function () {

	},

	/**
	 * 页面相关事件处理函数--监听用户下拉动作
	 */
	onPullDownRefresh: function () {

	},

	/**
	 * 页面上拉触底事件的处理函数
	 */
	onReachBottom: function () {

	},

	/**
	 * 用户点击右上角分享
	 */
	onShareAppMessage: function () {

	}
})

# 全局对象wx

# 获取用户信息(按钮)

// 判断用户是否已经授权
wx.getSetting({
  success: (result) => {
    const {authSetting} = result
    // 如果authSetting为true, 代表用户授权了
  }
})
// 获取用户信息:需要用户授权
wx.getUserInfo({
  success: (result)=>{},
});
myCallBack(data){
  const {userInfo} = data.mp.detail
  // 如果userInfo有值, 代表授权成功
}
}
<button open-type="getUserInfo" bindgetuserinfo="myCallBack">用户信息授权</button>

# 本地储存

// 异步:设置&添加 | 获取
wx.setStorage({key:'id',data:6})
wx.getStorage({key:'id',success,fail,complete})
wx.getStorageInfo({success,fail,complete})
// 同步:设置&添加
wx.setStorageSync({key:'id',data:6})

wx.getStorageInfoSync()

# 拨打电话

wx.makePhoneCall({ phoneNumber: '1340000' })

# 根据文件路径创建本地地址

wx.downloadFile({ url, success: (val) => resolve(val.tempFilePath), fail: reject })

# 本地拉取图片生成本地地址

wx.chooseImage({
  count: 1,
  sizeType: ['original', 'compressed'],
  sourceType: ['album', 'camera'],
  success (res) {
    // tempFilePath可以作为img标签的src属性显示图片
    const tempFilePaths = res.tempFilePaths
  }
})

# 从本地上传文件至服务器

wx.chooseImage({
  success (res) {
    // 本地文件地址
    const tempFilePaths = res.tempFilePaths
    wx.uploadFile({
      url: 'https://example.weixin.qq.com/upload', //仅为示例,非真实的接口地址
      filePath: tempFilePaths[0],
      name: 'file',
      formData: {
        'user': 'test'
      },
      success (res){
        const data = res.data
        //do something
      }
    })
  }
})

# 弹出新页面显示图片

wx.previewImage({
  current: '', // 当前显示图片的http链接
  urls: [] // 需要预览的图片http链接列表
})

# 动态改变页头

wx.setNavigationBarTitle({ title: 'xxxx' })
uni.setNavigationBarColor({
frontColor: "#ffffff",
backgroundColor: "#007AFF"
})

# 打开地图

wx.getLocation({
 type: 'gcj02', //返回可以用于wx.openLocation的经纬度
 success (res) {
   const latitude = res.latitude
   const longitude = res.longitude
   wx.openLocation({
     latitude,
     longitude,
     scale: 18
   })
 }
})

# 路由页面编程

<button bind:tap="goToListPage">点击跳转</button>
<!-- 或者 -->
<navigator url="/pages/index/index" >进入首页(tab页)</navigator>
<redirectTo url="/pages/index/index" >进入首页(tab页)</redirectTo>
goToLlistPage () {
  // 点击跳转到list页面(不能跳转tapBar页面)
  // 推入栈
  wx.navigateTo({url:'/pages/list/list'})
  // 重定向
  wx.redirectTo({url:'/pages/list/list'})
  // 跳转tapBar页面(并关闭非tapBar页面)
  wx.switchTab({url: '/pages/list/list'});
}

# 路由传参

// 父组件:传入index值为666
wx.navigateTo({url:'/pages/list/list?index=666'})
onLoad: function (options) {
	const {index} = options // 666
},

# 设置底部路由导航

// app.json
"tabBar": {
  "list": [ // 对应多个路由
    {	
      "pagePath": "pages/list/list", // 链接对应页面
      "text": "文与子",	// 链接文字
      "iconPath": "/images/tab/yuedu.png", // 链接未选中图片
      "selectedIconPath": "/images/tab/yuedu_hl.png" // 链接选中图片
    },
    {
      "pagePath": "pages/movies/movies",
      "text": "电影频道",
      "iconPath": "/images/tab/dianying.png",
      "selectedIconPath": "/images/tab/dianying_hl.png"
    }
  ]
}

# 小程序复用模板

# 定义模板

使用 name 属性,作为模板的名字。然后在<template/>内定义代码片段,如:pages/template/template.wxml

<template name="msgItem">
  <view>
    <text> {{index}}: {{msg}} </text>
    <text> Time: {{time}} </text>
  </view>
</template>

在模板的同级目录创建相同名称的样式文件,如:pages/template/template.wxss

.list{}

在模板的同级目录创建相同名称的js文件,如:pages/template/template.js

Page({
  data: {
      index: 0,
      msg: 'this is a template',
      time: '2016-09-15'
  }
})

# 使用模板

import可以在该文件中使用目标文件定义的template,在 index.wxml 中引用了 template.wxml,就可以使用template模板:pages/template/template.wxml

<import src="/pages/template/template.wxml" /><!-- 引入 -->
<template is="list_template" /><!-- 使用 -->

由于默认模板是不带有样式的,需要引入模板文件夹中的样式:pages/template/template.wxss

@import "/pages/template/template.wxss"

# 小程序自定义复用组件

开发者可以将页面内的功能模块抽象成自定义组件,以便在不同的页面中重复使用;也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护。自定义组件在使用时与基础组件非常相似。

# 定义组件

类似于页面,一个自定义组件由 json wxml wxss js 4个文件组成。要编写一个自定义组件,首先需要在 json 文件中进行自定义组件声明(将 component 字段设为 true 可将这一组文件设为自定义组件):

{ "component": true }

同时,还要在 wxml 文件中编写组件模板,在 wxss 文件中加入组件样式,它们的写法与页面的写法类似。

<!-- 这是自定义组件的内部WXML结构 -->
<view class="inner">
 	{{innerText}}
    <slot><!-- 插槽是使用时, 传入的所有内容 --></slot>
</view>
/* 这里的样式只应用于这个自定义组件 */
.inner {
  color: red;
}

在自定义组件的 js 文件中,需要使用 Component() 来注册组件,并提供组件的属性定义、内部数据和自定义方法。 组件的属性值和内部数据将被用于组件 wxml 的渲染,其中,属性值是可由组件外部传入的。更多细节参见 Component构造器 (opens new window)

Component({
  properties: {
    // 这里定义了innerText属性,属性值可以在组件使用时指定
    innerText: {
      type: String,
      value: 'default value',
    }
  },
  data: {
    // 这里是一些组件内部数据
    someData: {}
  },
  methods: {
    // 这里是一个自定义方法
    customMethod: function(){}
  }
})

# 使用组件

使用已注册的自定义组件前,首先要在页面的 json 文件中进行引用声明。此时需要提供每个自定义组件的标签名和对应的自定义组件文件路径

{
  "usingComponents": {
    "component-tag-name": "path/to/the/custom/component"
  }
}

这样,在页面的 wxml 中就可以像使用基础组件一样使用自定义组件。节点名即自定义组件的标签名,节点属性即传递给组件的属性值。

<view>
  <!-- 以下是对一个自定义组件的引用 -->
  <component-tag-name inner-text="Some text">
  	<!-- 这部分内容将被放置在组件 <slot> 的位置上 -->
    <view>这里是插入到组件slot中的内容</view>  
  </component-tag-name>
</view>

# 组件wxml的slot

在组件的wxml中可以包含 slot 节点,用于承载组件使用者提供的wxml结构。默认情况下,一个组件的wxml中只能有一个slot。需要使用多slot时,可以在组件js中声明启用。

Component({
  options: {
    multipleSlots: true // 在组件定义时的选项中启用多slot支持
  },
  properties: { /* ... */ },
  methods: { /* ... */ }
})

此时,可以在这个组件的wxml中使用多个slot,以不同的 name 来区分。

<!-- 组件模板 -->
<view class="wrapper">
  <slot name="before"></slot>
  <view>这里是组件的内部细节</view>
  <slot name="after"></slot>
</view>

使用时,用 slot 属性来将节点插入到不同的slot上。

<!-- 引用组件的页面模板 -->
<view>
  <component-tag-name>
    <!-- 这部分内容将被放置在组件 <slot name="before"> 的位置上 -->
    <view slot="before">这里是插入到组件slot name="before"中的内容</view>
    <!-- 这部分内容将被放置在组件 <slot name="after"> 的位置上 -->
    <view slot="after">这里是插入到组件slot name="after"中的内容</view>
  </component-tag-name>
</view>

# app.js定义共用数据

// app.js
App({
  data:{a:6}
})
// pages/index.js
const app = getApp()
console.log(app.data) // {a:6}
app.data.a = 7
console.log(app.data) // {a:7}

####### ###

# 小程序功能组件

# 提示框

wx.showToast({title:'内容',icon: 'success'});

# 背景音乐播放器

https://developers.weixin.qq.com/miniprogram/dev/api/media/background-audio/BackgroundAudioManager.html

// 获取唯一后台播放器实例
const audioPlayer = wx.getBackgroundAudioManager()
// 设置音乐标题(必填)
audioPlayer.title = 'music'
// 设置音乐地址(必填)
audioPlayer.src = 'http://music.163.com/song/media/outer/url?id=1319688149.mp3'
// 设置音乐图片
audioPlayer.coverImgUrl = 'http://p1.music.126.net/oErHMKLK12jQbr7k6xrlMA==/109951164344700503.jpg?param=130y130'
// 设置音乐歌手名
audioPlayer.singer = 'Mr.A'
// 设置专辑名
audioPlayer.epname = 'OVA'
// 设置音频开始播放的时间
audioPlayer.startTime = 5
// 当前音频的长度(只读)
audioPlayer.duration
// 当前音频的播放位置(只读)
audioPlayer.currentTime
// 当前是否暂停或停止(只读)
audioPlayer.paused
// 播放/ 暂停
isMusicPlay ? audioPlayer.play() : audioPlayer.pause()
// 监视音频暂停
audioPlayer.onPause(()=>{})
// 监视音频播放
audioPlayer.onPlay(()=>{})

# 菜单栏

wx.showActionSheet({
	itemList: ['分享到朋友圈', '分享到qq空间', '分享到微博'],
	success: (result) => {
		// 点击的下标
		const {tapIndex} = result
	}
})

# 点击分享(按钮)

<button class="forward_button"  open-type="share">转发此文章</button>

# 发送ajax请求

# 原生小程序发送请求

// 注意:默认不能使用http协议请求,需要在调试工具中关闭校验
var reqTask = wx.request({
	url: '', // 请求地址
	data: {}, // 请求参数
	header: {'content-type':'application/json'}, // 请求头
	method: 'GET', // 请求类型
	dataType: 'json', // 请求数据类型?
	responseType: 'text', // 返回数据类型?
	success: (result)=>{
		// 请求成功函数result 为结果
	},
	fail: ()=>{},
	complete: ()=>{}
});

# 使用FlyIo库发送请求

// ajax-fly请求配置文件
const Fly = require("./wx-fly.min")
const fly = new Fly
//定义公共headers
fly.config.headers={xx:5,bb:6,dd:7}
//设置超时
fly.config.timeout=10000;
//设置请求基地址
fly.config.baseURL="https://wendux.github.io/"
//设置公共的Get参数
fly.config.params={"token":"testtoken"};
//添加请求拦截器
fly.interceptors.request.use((request) => {
    //给所有请求添加自定义header
    request.headers["X-Tag"] = "flyio";
    //打印出请求体
    // console.log(request.body)

    //终止请求
    //var err=new Error("xxx")
    //err.request=request
    //return Promise.reject(new Error(""))

    //可以显式返回request, 也可以不返回,没有返回值时拦截器中默认返回request
    return request;
})
//添加响应拦截器,响应拦截器会在then/catch处理之前执行
fly.interceptors.response.use(
    (response) => {
        //只将请求结果的data字段返回
        return response.data
    },
    (err) => {
        //发生网络错误后会走到这里
        //return Promise.resolve("ssss")
        return err
    }
)
export default fly

# 小程序标签节点对象

# query 标签对象

返回一个 SelectorQuery 对象实例。在自定义组件或包含自定义组件的页面中。

// 创建一个query对象, 该对象专门用于获取标签节点
const query = wx.createSelectorQuery()
// 获取该标签boundingClientRect的所有属性
query.select('#the-id').boundingClientRect()
// 获取该标签selectViewport与scrollOffset属性
query.selectViewport().scrollOffset()
// 执行所有的请求, 请求结果按请求次序构成数组. 最终使用exec接受所有的属性数据
query.exec(function(res){
  res[0].top       // #the-id节点的上边界坐标 (boundingClientRect)
  res[1].scrollTop // 显示区域的竖直滚动位置 (selectViewport)
})

# query.NodesRef 节点信息

**boundingClientRect() **节点的布局位置的查询请求。其功能类似于 DOM 的 getBoundingClientRect

// 单个节点查询
wx.createSelectorQuery().select('#the-id').boundingClientRect(function(rect){
	rect.id      // 节点的ID
	rect.dataset // 节点的dataset
	rect.left    // 节点的左边界坐标
	rect.right   // 节点的右边界坐标
	rect.top     // 节点的上边界坐标
	rect.bottom  // 节点的下边界坐标
	rect.width   // 节点的宽度
	rect.height  // 节点的高度
}).exec()
// 多个节点查询
wx.createSelectorQuery().selectAll('.a-class').boundingClientRect(function(rects){
  rects.forEach(function(rect){
    rect.id      // 节点的ID
    rect.dataset // 节点的dataset
    rect.left    // 节点的左边界坐标
    rect.right   // 节点的右边界坐标
    rect.top     // 节点的上边界坐标
    rect.bottom  // 节点的下边界坐标
    rect.width   // 节点的宽度
    rect.height  // 节点的高度
  })
}).exec()

context() 节点的 Context 对象查询请求。

wx.createSelectorQuery().select('.the-video-class').context(function(res){
  console.log(res.context) // 节点对应的 Context 对象。如:选中的节点是 <video> 组件,那么此处即返回 VideoContext 对象
}).exec()

fields() 获取节点的相关信息。

wx.createSelectorQuery().select('#the-id').fields({
  id: true, // 是否返回节点 id
  dataset: true, // 是否返回节点 dataset
  mark: true, // 是否返回节点 mark
  rect: true, // 是否返回节点布局位置(left right top bottom)
  size: true, // 是否返回节点尺寸(width height)
  scrollOffset: true, // 是否返回节点的 scrollLeft scrollTop,节点必须是 scroll-view 或者 viewport
  properties: ['scrollX', 'scrollY'], // 指定属性名列表,返回节点对应属性名的当前属性值 (id class style 和事件绑定的属性值不可获取)
  computedStyle: ['margin', 'backgroundColor'], // 指定样式名列表,返回节点对应样式名的当前值
  context: true, // 是否返回节点对应的 Context 对象
  node: true, // 是否返回节点对应的 Node 实例
}, function (res) {
  res.dataset    // 节点的dataset
  res.width      // 节点的宽度
  res.height     // 节点的高度
  res.scrollLeft // 节点的水平滚动位置
  res.scrollTop  // 节点的竖直滚动位置
  res.scrollX    // 节点 scroll-x 属性的当前值
  res.scrollY    // 节点 scroll-y 属性的当前值
  // 此处返回指定要返回的样式名
  res.margin
  res.backgroundColor
  res.context    // 节点对应的 Context 对象
}).exec()

**node() **获取 Node 节点实例。目前支持 Canvas (opens new window) 的获取。

wx.createSelectorQuery().select('.canvas').node(function(res){
	console.log(res.node) // 节点对应的 Canvas 实例。
}).exec()

# query.SelectorQuery 节点查询

**exec() **执行所有的请求。请求结果按请求次序构成数组,在callback的第一个参数中返回。

//....
query.exec(function(res){
  res[0].top       // #the-id节点的上边界坐标 (boundingClientRect)
  res[1].scrollTop // 显示区域的竖直滚动位置 (selectViewport)
})

in() 将选择器的选取范围更改为自定义组件 component 内。

Component({
  queryMultipleNodes (){
    const query = wx.createSelectorQuery().in(this)
    query.select('#the-id').boundingClientRect(function(res){
      res.top // 这个组件内 #the-id 节点的上边界坐标
    }).exec()
  }
})

select() 在当前页面下选择第一个匹配选择器 selector 的节点。

query.select('#box')

selectAll() 在当前页面下选择匹配选择器 selector 的所有节点。

query.selectAll('.box')

selectViewport() 显示区域。可用于获取显示区域的尺寸、滚动位置等信息。

query.selectViewport()

# 小程序画布

# canvas的标签

<canvas
    type="2d" // 指定 canvas 类型,支持 2d (2.9.0) 和 webgl (2.7.0), 必选项
    bindtouchstart="onStart" // 手指触摸动作开始
    bindtouchmove="onMove" // 手指触摸后移动
    bindtouchend="onEnd" // 手指触摸动作结束
    bindtouchcancel="onCancel" // 手指触摸动作被打断,如来电提醒,弹窗
    bindlongtap="onLongTap" // 手指长按 500ms 之后触发,触发了长按事件后进行移动不会触发屏幕的滚动
    binderror="onError" // 当发生错误时触发 error 事件,detail = {errMsg}
/>

# 在js中获取画布的实例

// 创建画布
async createCanvas (){
  // 由于画布并没有像其他的一样支持小程序独有的 rpx 自适应尺寸单位, 所以要获取rpx适配的值
  // 计算公式为 设备视口宽度 / 设计图宽度(375) = 设计图中的一像素适配设备视口宽度的几像素
  const rpx = wx.getSystemInfoSync().windowWidth / 375
  // 获取画布实例(node), 宽高等
  const canvasRect = await getCanvasRect('#canvas')
  const {width, height, node} = canvasRect
  // 创建画笔
  const ctx = node.getContext('2d')
  console.log(width, height, node)
}

# 新版2D Canvas 插入图片的方法

// 创建图片对象, src地址为本地地址, onload回调为图片加载完毕后, 在该函数中绘制图形
const labelUrl123=node.createImage()
labelUrl123.src= labelUrl
labelUrl123.onload = () => ctx.drawImage(labelUrl123, 0, 0, 35*rpx, 35*rpx);