1、初識微信小程序
- 小程序爲什麼存在?爲企業或個人提供便利的用戶連接工具;它可以在一定程度上可以替代掉部分手機APP的作用(用完即走)。
- 產品設計標準:小而美、開發週期較短。
- 張小龍親自“引爆”微信小程序(附演講全文)
2、微信小程序開發前準備
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 等應用。
本篇結束,感謝關注!!!