1、初识微信小程序
- 小程序为什么存在?为企业或个人提供便利的用户连接工具;它可以在一定程度上可以替代掉部分手机APP的作用(用完即走)。
- 产品设计标准:小而美、开发周期较短。
- 张小龙亲自“引爆”微信小程序(附演讲全文):
张小龙亲自“引爆”微信小程序 (附演讲全文)www.sohu.com/a/122892706_480349[图片上传失败...(image-8cbb8-1655106875266)]
2、微信小程序开发前准备
- 翻阅微信小程序官方文档
微信开放文档developers.weixin.qq.com/miniprogram/dev/framework/
- 下载、安装“微信者开发工具”
稳定版 Stable Build 更新日志developers.weixin.qq.com/miniprogram/dev/devtools/stable.html
- 注册一个小程序账号(管理后台)
3、小程序管理后台的基本操作
(一)版本管理
- 小程序认证:填写基本信息、注意选择行业类目、备案付费300元。
- 小程序有三个版本:开发版、审核版、线上版(默认代码体积不能超过2M)。
- 小程序项目中用到的静态资源,可以放到CDN上,以减小代码体积。
(二)成员管理
- 管理员(1人),是注册账号的微信用户。
- 项目成员(15人),可以登录小程序管理后台,开发者必须是项目成员。
- 体验成员(15人),只有体验的权限,没有开发的权限。
(三)开发管理
- AppID,相当是小程序的身份证号码,创建项目、调试项目、小程序之间的跳转都要用到,还有比如支付等也要用到。
- AppSecret,小程序密钥,一般要给后端,在登录、支付等功能中都要用到。
- Request 地址,就是api 的 baseURL,本地开发时可以关闭https验证,上线时一定要小程序管理后台中添加上这个地址,并且要求https协议的。
4、微信开发者工具的基本使用
- 如何创建新项目?
- 如何导入旧项目?
- 调试项目(真机调试、微信开发者工具调试)
- 如何上传代码至开发版?
- 关闭“开发环境下校检 https”的限制
- 注意小程序api 版本库和微信版本之间兼容性问题。
5、认识四种文件
- .wxml,类似 HTML 的角色。
- .wxss,具有 CSS 大部分的特性,小程序在 WXSS 也做了一些扩充和修改。
- .js,编写脚本与用户交互,响应用户的点击、获取用户的位置等等。
- .json,是一种数据格式,并不是编程语言,在小程序中,JSON扮演的静态配置的角色。
6、自带配置风格的小程序
微信小程序根目录下的 app.json
文件用来对微信小程序进行全局配置,决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等。
{
"pages": [
"pages/index/index",
"pages/books/books"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "好程序员",
"navigationBarTextStyle": "black"
},
"tabBar": {
"color": "#aaaaaa",
"selectedColor": "#ff0000",
"list": [
{
"pagePath": "pages/index/index",
"text": "福利",
"iconPath": "/assets/tabbar/index.png",
"selectedIconPath": "/assets/tabbar/index-on.png"
},
{
"pagePath": "pages/books/books",
"text": "书城",
"iconPath": "/assets/tabbar/book.png",
"selectedIconPath": "/assets/tabbar/book-on.png"
}
]
}
}
7、App类与应用级别的生命周期
注册小程序。接受一个 Object
参数,其指定小程序的生命周期回调等。App()
必须在 app.js
中调用,必须调用且只能调用一次。不然会出现无法预期的后果。
App({
// 整个应用程序的入口
onLaunch() {
wx.login({
success: res => console.log('登录', res.code)
})
},
globalData: {
userInfo: null,
}
})
8、Page类与页面级别的生命周期
Page({
data: { },
onLoad: function (options) { },
onReady: function () {},
onShow: function () { },
onHide: function () {},
onUnload: function () {},
onPullDownRefresh: function () { },
onReachBottom: function () { },
onShareAppMessage: function () { }
})
9、Component类与组件级别的生命周期
Component({
properties: { }, // 父组件传递过来的属性
data: { }, // 自有的状态数据
ready: function(){ }, // 生命周期
methods: { } // 自有的方法
})
10、自定义Cate
组件
# cate.wxml
<view class="cate">
<text
wx:for='{{list}}'
class="cate_item {{value===item.cate?'on':''}}"
wx:key='item'
data-cate='{{item.cate}}'
bindtap='tapClick'>
{{item.cate_zh}}
</text>
</view>
# cate.wxss
.cate_item {
padding: 0 10rpx;
display: inline-block;
border: 1rpx solid #ccc;
line-height: 45rpx;
}
.cate_item.on {
color: red;
}
# cate.js
Component({
properties: {
list: {type: Array, value: [] },
value: {type:String, value: ''}
},
methods: {
tapClick(e) {
// 父子组件事件通信
// this.triggerEvent('input', e.target.dataset.cate)
// model 双向绑定的写法
this.setData({value: e.target.dataset.cate})
}
},
// 生命周期
lifetimes: {},
pageLifetimes: {}
})
# cate.json
{
"component": true
}
11、使用自定义封装的Cate
组件
# study.json
{
"usingComponents": {
"qf-cate": "/components/cate/cate"
}
}
<qf-cate list='{{cateList}}' model:value='{{curCate}}'></qf-cate>
12、列表渲染
<block
wx:for="{{actList}}"
wx:for-item="act"
wx:key="act"
wx:for-index="idx"
>
<view class="row">
<text>{{idx+1}}</text>
<text>{{act.id*100}}</text>
<text>{{act.title}}</text>
</view>
</block>
13、条件渲染
<view wx:if='{{idx===1}}'>第一行文字</view>
<view wx:elif='{{idx===2}}'>第二行文字</view>
<view wx:elif='{{idx===3}}'>第三行文字</view>
<view wx:else>第四行文字</view>
<button bindtap='toggle'>切换</button>
<!-- 显示与隐藏、类似v-show -->
<view hidden='{{bol}}'>第十行文字</view>
14、动态样式
<view class="box {{bol?'box':'box2'}}"></view>
<view style='color:red;font-size:{{size+"rpx"}}'>动态样式文字</view>
15、事件绑定(bind / catch)
<!-- 事件绑定、冒泡、事件传参、事件对象 -->
<view bindtap='clickOuter'>
<view
data-msg='hello bind'
bindtap='click1'>
测试事件(bind)
</view>
<view catchtap='click2'>测试事件(catch绑定)</view>
</view>
Page({
data: {},
click1(e) {
console.log('click1', e)
console.log('arg', e.target.dataset.msg)
},
click2(e) {
console.log('click2', e)
},
clickOuter(e) {
console.log('outer', e)
}
})
16、表单绑定(单向绑定、双向绑定)
<view>
<input type="text" value='{{username}}' bindblur='usernameChange' />
<input type="text" model:value='{{password}}' />
<button bindtap='submit'>提交</button>
</view>
Page({
data: {
username: '小明',
password: '123',
},
// 手动取值
usernameChange(e) {
console.log('e', e.detail.value)
this.setData({username: e.detail.value})
},
submit() {
const data = {
username: this.data.username,
password: this.data.password
}
console.log('提交', data)
}
})
17、微信小程序动画(最新写法)
<view class="abc"></view>
<button bindtap='startAnim'>开始你的表演</button>
Page({
data: { },
startAnim() {
// 创建动画
// 第1参数是节点选择器
// 第2个参数是动态帧(数组)
// 第3个是过滤时间
// 第4个参数是回调参数,用于清除动画,这是一种性能优化方案
this.animate('.abc', [
{ opacity: 1.0, rotate: 0, backgroundColor: '#FF0000', scale: [1,1] },
{ opacity: 0.5, rotate: 45, backgroundColor: '#00FF00', scale: [0.6,0.6]},
{ opacity: 0.2, rotate: 80, backgroundColor: '#FF0000',scale: [0.2,0.2] },
], 5000, ()=> {
// 清除动画
// 它的第二个参数,用于控制动画的样式是否保留最后一帧的样式,默认是保留的
this.clearAnimation('.abc', {
opacity: false,
rotate: false,
// backgroundColor: false
}, ()=> {
console.log("清除动画成功")
})
})
}
})
18、使用Canvas画布(最新写法)
<canvas type="2d" class="ad" id="myCanvas"/>
<button bindtap='save'>保存到相册</button>
Page({
data: { },
rpx2px(arg) {
const res = wx.getSystemInfoSync()
return res.screenWidth * arg / 750
},
// 用于支持最新drawImage()写法
async getImage(path) {
const c = wx.createOffscreenCanvas({type: '2d'})
const image = c.createImage()
await new Promise(resolve => {
image.onload = resolve
image.src = path
})
return image
},
onReady() {
const $ = wx.createSelectorQuery()
$.select('.ad')
.fields({ node: true, size: true })
.exec((res) => {
this.canvas = res[0].node
const ctx = canvas.getContext('2d')
// 第1步,绘制一个和画布一样大的矩形
ctx.fillStyle = 'orange'
ctx.fillRect(0, 0, this.rpx2px(750), this.rpx2px(500))
// 第2步,绘制标题文字
ctx.font = "bold 25px serif"
ctx.fillStyle = 'white'
ctx.fillText('樱桃小丸子很可爱', this.rpx2px(0), this.rpx2px(50))
// 第3步,绘制图片
this.getImage('/assets/girl.png').then(img=>{
ctx.drawImage(img,50,50)
})
})
},
// 保存到相册
save() {
// 把canvas画布转换成临时路径
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: this.rpx2px(750),
height: this.rpx2px(500),
destWidth: 1500,
destHeight: 1000,
canvas: this.canvas,
success(res) {
// 把临时路径中的图片保存到相册
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath
})
}
})
}
})
19、小程序初次启动时请求用户授权地理定位
# app.js
App({
onLaunch() {
// 获取用户的地理定位的授权
wx.getSetting({
success(res) {
console.log('当前用户的授权列表', res.authSetting)
// 如果用户未授权地理定位
if (!res.authSetting['scope.userLocation']) {
// 无须用户触发,直接弹框请求用户同意使用地理定位
// 当用户拒绝过一次后,这段代码没用了
wx.authorize({
scope: 'scope.userLocation',
success (res) {
console.log('地理定位:用户点击了同意', res)
},
fail (err) {
console.log('地理定位:用户点击了拒绝', res)
}
})
}
}
})
}
})
# app.json
{
"pages": [],
"permission": {
"scope.userLocation": {
"desc": "为了给你更好的服务,希望使用你的地理定位"
}
}
}
20、使用地理定位
<button bindtap='getLngLat'>获取用户经纬度</button>
<map class="map"
longitude='{{latLng.longitude}}'
latitude='{{latLng.latitude}}'
></map>
<button bindtap='navTo'>导航去这里</button>
Page({
data: {
latLng: {}
},
getLngLat() {
var that = this
wx.getSetting({
success(res){
if(res.authSetting['scope.userLocation']) {
// 如果用户已经同意过地理定位,直接获取经纬度
wx.getLocation({
success(res) {
console.log('用户位置', res)
that.setData({latLng: res})
}
})
}else{
// 如果用户已经拒绝过地理定位,我们要打开微信内置的设置页面,让用户自己选择授权
wx.openSetting({
success(res) {
console.log('用户手动选择授权', res)
}
})
}
}
})
},
navTo() {
wx.openLocation({
latitude: this.data.latLng.latitude,
longitude: this.data.latLng.longitude,
name: '深圳',
address: '广东省深圳市'
})
}
})
21、onShareAppMessage 实现转发
<button open-type='share'>
<view>拼团</view>
</button>
Page({
data: {},
// 实现转发的两种方案(胶囊按钮、button.open-type='share')
onShareAppMessage(e) {
console.log('转发', e)
if(e.from==='menu') {
return {
title: '招聘年薪百万',
path: 'pages/listen/listen',
imageUrl: 'https:70.jpg.webp'
}
}else if(e.from==='button') {
return {
title: '我正在拼团...',
path: 'pages/listen/listen',
imageUrl: 'https://img20.0.jpg.webp'
}
}
}
})
22、globalData 全局数据
App({
globalData: { msg: 'hello' }
})
const app = getApp()
Page({
data: {
msg: app.globalData.msg
},
updateMsg() {
app.globalData.msg = '456'
this.setData({msg: app.globalData.msg})
}
})
<view>{{msg}}</view>
<button bindtap='updateMsg'>修改msg</button>
23、onPageScroll 监听页面滚动
<!-- 使用scrollTop -->
<button bindtap='backToTop'>回到指定位置</button>
App({
// 页面滚动
onPageScroll(e) {
console.log('页面滚动', e.scrollTop)
},
// 使用scrollTop滚动到页面的任何位置
backToTop() {
wx.pageScrollTo({
scrollTop: 133,
duration: 2000
})
}
})
24、<match-media> 实现媒体查询
<!-- 媒体查询 -->
<match-media min-width="315" max-width="600">
<view>根据媒体设备来决定我是否显示</view>
</match-media>
25、<movable-area> 实现拖拽
<movable-area class="area">
<movable-view
direction='all'
x='{{point.x}}'
y='{{point.y}}'
bindchange='areaChange'>
<view class="area-text">text</view>
</movable-view>
</movable-area>
Page({
data: {
point: {x:0,y:0}
},
areaChange(e) {
this.setData({point: e.detail})
}
})
26、功能极其强大的 <button>表单组件
<!-- button是表单,功能极其丰富 -->
<button open-type='contact'>联系客服</button>
<button open-type='getPhoneNumber' bindgetphonenumber='getMobile'>登录</button>
<!-- <button open-type='getUserInfo' bindgetuserinfo='getUserInfo'>上传图像</button> -->
<button bindtap='getUser'>获取用户信息</button>
<button open-type='launchApp'>打开抖音</button>
<button open-type='openSetting'>打开授权页面</button>
<button open-type='feedback'>投诉建议</button>
Page({
data: {},
getMobile(e) {
// 目前已不支持个人版本的小程序
console.log('获取手机号', e)
},
// 获取用户信息,会弹框请求用户授权
// 即将过时,建议使用wx.getUserProfile获取用户信息
getUserInfo(e) {
console.log('获取用户信息', e)
},
getUser() {
wx.getUserProfile({
desc: '用于完善会员资料',
success(e) {
console.log('最新的用户信息', e)
// 拿到用户信息之后,要调接口发送给后端数据库
// 把用户保存在业务数据库
}
})
}
})
27、使用 <picker> 组件选择省市区
<view class="section">
<view class="section__title">省市区选择器</view>
<picker
mode="region"
bindchange="bindRegionChange"
value="{{region}}"
custom-item="{{customItem}}"
>
<view class="picker">
当前选择:{{region[0]}},{{region[1]}},{{region[2]}}
</view>
</picker>
</view>
Page({
data: {
region: ['广东省', '广州市', '海珠区'],
customItem: '全部',
},
bindRegionChange(e) {
console.log('region picker', e)
this.setData({region: e.detail.value})
}
})
28、<picker> 组件使用再举例
<view class="section">
<view class="section__title">选择品类</view>
<picker
mode="selector"
bindchange="bindCateChange"
value="{{cateIdx}}"
range='{{cateArr}}'
range-key='cate_zh'
>
<view class="picker">
当前选择:{{cateArr[cateIdx].cate_zh}}
</view>
</picker>
</view>
Page({
data: {
cateArr: [
{id:0,cate:'all',cate_zh:'全部'},
{id:1,cate:"car",cate_zh:"汽车生活"},
{id:1,cate:"office",cate_zh:"办公用品"}
],
cateIdx: 0
},
bindCateChange(e) {
console.log('cate picker', e)
this.setData({cateIdx: parseInt(e.detail.value)})
}
})
29、使用 <audio> 音频组件
<audio
poster="http://y.592000.png"
name="此时此刻"
author="许巍"
src="http://ws.stream.qqmusic.qq.com/M500001VfvsJ21xFqb.mp3?guid=ffffffff82def4af4b12b3cd9337d5e7&uin=346897220&vkey=6292F51E1E384E06DCBDC9AB7C49FD713D632D313AC4858BACB8DDD29067D3C601481D36E62053BF8DFEAF74C0A5CCFADD6471160CAF3E6A&fromtag=46"
id="myAudio"
controls
loop>
</audio>
<button bindtap='startPlay'>播放</button>
Page({
startPlay() {
const audioCtx = wx.createInnerAudioContext()
console.log('ctx', audioCtx)
audioCtx.play()
}
})
30、使用 <camera> 相机组件
<camera device-position="back" flash="off" binderror="error" style="width: 100%; height: 300px;"></camera>
<button bindtap='takeCamera'>拍照</button>
<image src='{{avatar}}'></image>
Page({
data: {avatar:''},
takeCamera() {
const ctx = wx.createCameraContext()
ctx.takePhoto({
quality: 'high',
success: (res) => {
this.setData({
avatar: res.tempImagePath
})
}
})
}
})
31、小程序路由跳转
<button bindtap='skipToTabPage'>跳转到Tab页</button>
<button bindtap='skipToNotTabPage'>跳转到非Tab页</button>
Page({
// 跳转到Tab页,使用switchTab,不能传参
skipToTabPage() {
wx.switchTab({
url: '/pages/listen/listen'
})
},
// 跳转到非Tab页,使用navigateTo,可以传参
// 在另一个页面中,使用 onLoad 生命周期来接收参数
skipToNotTabPage() {
wx.navigateTo({
url: '/pages/user/user?id=123&name=abc'
})
}
})
32、自定义ActionSheet
<button bindtap='selectMethod'>兑换礼品</button>
Page({
selectMethod() {
wx.showActionSheet({
itemList: ['使用积分兑换', '直接支付'],
success (res) {
console.log('用户选择的兑换方式是:', res.tapIndex)
}
})
}
})
33、使用小程序的功能 API
<button bindtap='testWXApi'>测试API</button>
Page({
testWXApi() {
wx.setNavigationBarTitle({title:'1234'})
wx.setBackgroundColor({ backgroundColor: '#ff0000' })
wx.hideTabBar()
}
})
34、从手机相册中选取照片
<button bindtap='selectAvatar'>选择照片</button>
Page({
selectAvatar() {
// 可以先使用wx.getSetting先判断当前用户是否有访问相机和相册的权限
wx.chooseImage({
count: 9,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success (res) {
// tempFilePath可以作为img标签的src属性显示图片
const tempFilePaths = res.tempFilePaths
console.log('tempFilePaths', tempFilePaths)
}
})
}
})
35、微信小程序支付(伪代码)
<button bindtap='pay'>立即支付</button>
Page({
pay () {
// 前提:先开通微信支付平台(收款平台),小程序要认证
// 1、在小程序管理后台绑定已有的支付账号
// 2、使用wx.request把订单信息发送给业务服务器,后端会返回支付信息
// 3、使用wx.requestPayment()请求完成支付
}
})
36、小程序中实现复制黏贴、打电话、扫码功能
<view bindlongtap='copy'>订单号:QF20120902093203023</view>
<button bindtap='call'>打电话</button>
<button bindtap='scan'>扫码</button>
Page({
// 复制黏贴
copy() {
wx.setClipboardData({
data: 'QF20120902093203023'
})
},
call() {
wx.makePhoneCall({
phoneNumber: '0755-89890909'
})
},
scan() {
wx.scanCode({
scanType: 'barCode',
success(res){
console.log('扫码结果', res)
}
})
}
})
37、下拉刷新与触底加载
Page({
// 触底加载
onReachBottom() {
console.log('到底了,我准备调接口')
},
// 下拉刷新
onPullDownRefresh() {
console.log('正在下拉刷新')
setTimeout(()=>{
// 当调接口完成时,手动停止掉下拉刷新
wx.stopPullDownRefresh()
}, 1000)
},
})
# .json 局部配置
{
"navigationStyle": "custom",
"onReachBottomDistance": 100,
"enablePullDownRefresh": true
}
38、Request 封装
const baseUrl = 'http://localhost:8888'
const version = '/api/v1'
module.exports = function(url,method,data) {
return new Promise(function(resolve, reject){
wx.request({
url: baseUrl+version+url,
data,
method,
header: {
Authorization: wx.getStorageSync('token')
},
success (res) {
// 成功后数据过滤
resolve(res.data.data)
},
fail(err) {
wx.showToast({title:'网络异常'})
reject(err)
}
})
})
}
39、微信小程序的登录流程
# app.js
const api = require('./utils/api')
App({
// 整个应用程序的入口
onLaunch() {
// 登录
wx.login({
success: res => {
console.log('登录', res.code)
// 使用 wx.request 调接口,用code换取token
api.fetchLogin({code: res.code}).then(res=>{
// console.log('登录token', res)
wx.setStorageSync('token', res.token)
})
}
})
}
})
# Node.js + Koa 代码示例接口
const axios = require('../utils/axios')
const jwt = require('../utils/jwt')
// 引入model
const userModel = require('../model/user')
class UserController {
// 登录接口
static async login(ctx) {
// 接收入参
console.log('post body', ctx.request.body)
let { code } = ctx.request.body
console.log('code', code)
// 第1步,用code+appid+secret换取微信服务器的openid+session-key
const res = await axios({
url: '/sns/jscode2session',
method: 'get',
params: {
appid: 'w2x2f8b3892386e5e2ccf',
secret: '345bc1b923ae7423bbf28146e31ff372e',
js_code: code,
grant_type: 'authorization_code'
}
})
// 第2步,openid不能重复入库
const list = await userModel.find({openid: res.openid})
if(list.length > 0) {
const token = jwt.createToken(res)
ctx.body = {
err: 0,
msg: 'success',
data: { token }
}
}else{
const ele = {
openid: res.openid,
}
await userModel.insertMany([ele])
const token = jwt.createToken(res)
ctx.body = {
err: 0,
msg: 'success',
data: { token }
}
}
}
}
module.exports = UserController
40、写在最后
微信小程序原生写法比较麻烦,推荐使用 Taro 3.0
开发微信小程序,它不仅支持 React风格、也支持 Vue 和 Vue3.0 的语法风格。
Taro
是一个开放式跨端跨框架解决方案(由京东凹凸实验室开源),支持使用 React/Vue/Nerv 等框架来开发 微信、京东、百度、支付宝、字节跳动、QQ小程序、H5、RN 等应用。
网友评论