微信小程序原生開發入門指南

1、初識微信小程序

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 等應用。


本篇結束,感謝關注!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章