小程序模板網(wǎng)

喂,快給我打一個(gè)小程序預(yù)覽碼

發(fā)布時(shí)間:2018-07-18 09:00 所屬欄目:小程序開(kāi)發(fā)教程

需求

開(kāi)發(fā)小程序的朋友們隨時(shí)都會(huì)聽(tīng)到一句話(huà):“喂,快給我打一個(gè)xxx環(huán)境的預(yù)覽碼”,無(wú)論你正在干什么,都得趕緊地回一句:“稍等,這就給你打碼……”

然后苦逼的你build了一個(gè)xxx環(huán)境的包,打開(kāi)了微信開(kāi)發(fā)者工具,點(diǎn)了一下預(yù)覽,等了一下,預(yù)覽碼出來(lái)了,你復(fù)制丟給你的爸爸們。

終于有一天,你正在專(zhuān)心致志做一些不可描述的事情時(shí),“喂,快給我打一個(gè)xxx環(huán)境的預(yù)覽碼”,這時(shí)你內(nèi)心怒吼了一句:“老子不給你打碼!你自己打去!”

于是就有了這個(gè)需求,要搞個(gè)東西讓爸爸們自主打碼,嗯,應(yīng)該就是只有一個(gè)按鈕,點(diǎn)一下就可以出現(xiàn)預(yù)覽二維碼的東西,意淫了一下應(yīng)該是這樣的:

 

 

沒(méi)錯(cuò)!就這樣干!

規(guī)劃一下

干大事就要從胡思亂想開(kāi)始,現(xiàn)在來(lái)想想要搞成這個(gè)功能,需要做點(diǎn)什么準(zhǔn)備工作吧。

找微信開(kāi)發(fā)者工具的接口
最重要的事情莫過(guò)于看看微信開(kāi)發(fā)者工具有沒(méi)有給我們提供這樣的接口讓我們?nèi)ゲ僮?,?jīng)過(guò)一番查閱文檔我們會(huì)發(fā)現(xiàn),果然有!


會(huì)發(fā)現(xiàn),文檔給我們提供了兩種方式的接口,命令行調(diào)用以及HTTP調(diào)用。有了接口,一切都好辦了,無(wú)非就是調(diào)一下接口,拿到二維碼,貼到頁(yè)面上去而已嘛,很簡(jiǎn)單。

梳理開(kāi)發(fā)流程
我們就把這個(gè)簡(jiǎn)單的事情,用流程圖說(shuō)明一下:

所需技術(shù)

工欲善其事,必先利其器,我們要搞這個(gè)東西,還是先要把用到的技術(shù)整理一下。

  1. 微信開(kāi)發(fā)者工具
  2. 一個(gè)小程序項(xiàng)目(這里以一個(gè)mpvue項(xiàng)目為例子)
  3. 前端vue + vux,這里前端沒(méi)什么需要做的東西,這樣的搭配純屬是因?yàn)楸緛?lái)就正在做移動(dòng)端的東西,直接拿來(lái)用而已。
  4. 后端koa2,當(dāng)然后端用什么都可以,這里選擇koa2,純屬是因?yàn)槲乙膊粫?huì)用別的……
  5. 前后端HTTP請(qǐng)求統(tǒng)一用axios
  6. 涉及到node操作命令行需要用到shelljs

好像沒(méi)別的東西了,用到了再說(shuō)吧。

擼起袖子從后端開(kāi)始

為了省事,直接把前后端的東西放在一起。項(xiàng)目目錄:

 

可以看到server這個(gè)目錄下放的都是后端的東西。

server/index.js
先看看入口文件index.js,從這里我們可以知道后端要做兩件事情,第一要能訪(fǎng)問(wèn)到前端build出來(lái)的靜態(tài)資源,第二要能與前端通過(guò)HTTP接口進(jìn)行交互。見(jiàn)代碼:

const path = require('path')
const Koa = require('koa')
const koaStatic = require('koa-static')
const bodyParser = require('koa-bodyparser')
const router = require('./router')
const app = new Koa()
const port = 9871
app.use(bodyParser())
// 處理靜態(tài)資源 這里是前端build好之后的目錄
app.use(koaStatic(
  path.resolve(__dirname, '../dist')
))
// 路由處理接口
app.use(router.routes()).use(router.allowedMethods())
// 監(jiān)聽(tīng)端口
app.listen(9871)
console.log(`[demo] start-quick is starting at port ${port}`)

靜態(tài)資源方面的話(huà)使用koa-static即可,重點(diǎn)是怎樣給前端提供接口,這就要看路由了。

server/router/index.js

const Router = require('koa-router')
// 業(yè)務(wù)邏輯
const wx = require('../controller/wx')
const router = new Router({
  // 接口前綴 比如open接口 請(qǐng)求路徑就是/api/open
  prefix: '/api'
})
router.get('/open', wx.open)
  .get('/login', wx.login)
  .get('/preview', wx.preview)
  .get('/build', wx.build)
module.exports = router

這里可以清晰看到,后端提供了四個(gè)接口,但具體每個(gè)接口的業(yè)務(wù)邏輯則封裝在controller里的wx.js,如果以后還有別的業(yè)務(wù)邏輯,就在controller加相應(yīng)的模塊即可。

server/controller/wx.js

// 微信開(kāi)發(fā)者工具接口調(diào)用邏輯
const {open, login, preview, build} = require('../utli/wxToolApi')
// 處理成功失敗返回格式的工具
const {successBody, errorBody} = require('../utli')
class WxController {
  /**
   * 根據(jù)環(huán)境對(duì)mpvue項(xiàng)目進(jìn)行打包
   * @returns {Promise<void>}
   */
  static async build (ctx) {
    // 前端傳過(guò)來(lái)的get參數(shù)
    const query = ctx.request.query
    if (!query || !query.env) {
      ctx.body = errorBody(null, '構(gòu)建項(xiàng)目失敗')
      return
    }
    const [err, data] = await build(query.env)
    ctx.body = err ? errorBody(err, '構(gòu)建項(xiàng)目失敗') : successBody(data, '構(gòu)建項(xiàng)目成功')
  }
  /**
   * 打開(kāi)微信開(kāi)發(fā)者工具
   * @returns {Promise<void>}
   */
  static async open (ctx) {
    const [err, data] = await open()
    ctx.body = err ? errorBody(err, '打開(kāi)微信開(kāi)發(fā)者工具失敗') : successBody(data, '打開(kāi)微信開(kāi)發(fā)者工具成功')
  }
  /**
   * 登錄微信開(kāi)發(fā)者工具
   * @returns {Promise<void>}
   */
  static async login (ctx) {
    const [err, data] = await login()
    ctx.body = err ? errorBody(err, '登錄二維碼返回失敗') : successBody(data, '登錄二維碼返回成功')
  }
  /**
   * 查看預(yù)覽碼
   * @returns {Promise<void>}
   */
  static async preview (ctx) {
    const [err, data] = await preview()
    ctx.body = err ? errorBody(err, '預(yù)覽二維碼返回失敗') : successBody(data, '預(yù)覽二維碼返回成功')
  }
}
module.exports = WxController

為了代碼更加清晰,這里將具體操作微信開(kāi)發(fā)者工具的接口邏輯抽到util/wxToolApi.js里去了,僅僅處理怎樣以統(tǒng)一格式返回給前端。
util/wxToolApi.js

const {promiseWrap, successBody, errorBody} = require('../utli')
const {INSTALL_PATH, PROJECT_PATH, PORT_PATH, PORT_FILE_NAME, HOST} = require('../const')
const {readFile} = require('../utli/nodeApi')
const shell = require('shelljs')
const axios = require('axios')
module.exports = {
  /**
   * 根據(jù)環(huán)境對(duì)mpvue項(xiàng)目進(jìn)行打包
   * @param env [doc, pre, prd]
   * @returns {*}
   */
  build (env) {
    return promiseWrap(new Promise((resolve, reject) => {
      // 進(jìn)入項(xiàng)目目錄
      shell.cd(PROJECT_PATH)
      // 執(zhí)行打包命令
      shell.exec(`npm run build:${env}`, function (code, stdout, stderr) {
        resolve(stdout)
      })
    }))
  },
  /**
   * 打開(kāi)微信開(kāi)發(fā)者工具
   * @returns {*}
   */
  open () {
    return promiseWrap(new Promise((resolve, reject) => {
      // 進(jìn)入項(xiàng)目目錄
      shell.cd(INSTALL_PATH)
      // 執(zhí)行微信開(kāi)發(fā)者工具接口“命令行啟動(dòng)工具”
      shell.exec(`cli -o ${PROJECT_PATH}`, function (code, stdout, stderr) {
        if (stderr) return reject(stderr)
        resolve(stdout)
      })
    }))
  },
  /**
   * 獲取微信開(kāi)發(fā)者工具端口號(hào)
   * @returns {Promise<*>}
   */
  async getPort () {
    shell.cd(PORT_PATH)
    // http 服務(wù)在工具啟動(dòng)后自動(dòng)開(kāi)啟,HTTP 服務(wù)端口號(hào)在用戶(hù)目錄下記錄,可通過(guò)檢查用戶(hù)目錄、檢查用戶(hù)目錄下是否有端口文件及嘗試連接來(lái)判斷工具是否安裝/啟動(dòng)。
    const [err, data] = await readFile(PORT_FILE_NAME)
    return err ? errorBody(err, '讀取端口號(hào)文件失敗') : successBody(data, '讀取端口號(hào)文件成功')
  },
  /**
   * 微信開(kāi)發(fā)者工具進(jìn)行登錄
   * @returns {*}
   */
  login () {
    return promiseWrap(new Promise(async (resolve, reject) => {
      // 獲取端口號(hào)
      const portData = await module.exports.getPort()
      if (portData.code !== 0) {
        reject(portData)
        return
      }
      const port = portData.data
      axios.get(`http://${HOST}:${port}/login?format=base64`)
        .then(res => {
          resolve(res.data)
        })
        .catch(e => {
          reject(e)
        })
    }))
  },
  /**
   * 微信開(kāi)發(fā)者工具獲取預(yù)覽碼
   * @returns {*}
   */
  preview () {
    return promiseWrap(new Promise(async (resolve, reject) => {
      const portData = await module.exports.getPort()
      if (portData.code !== 0) {
        reject(portData)
        return
      }
      const port = portData.data
      axios.get(`http://${HOST}:${port}/preview?format=base64&projectpath=${encodeURIComponent(PROJECT_PATH)}`)
        .then(res => {
          resolve(res.data)
        })
        .catch(e => {
          reject(e)
        })
    }))
  }
}

這里有一點(diǎn)需要注意,為什么只有open接口需要用命令行調(diào)用方式?那是因?yàn)镠TTP調(diào)用方式必須加端口,比如open接口

# 打開(kāi)工具
http://127.0.0.1:端口號(hào)/open
# 打開(kāi)/刷新項(xiàng)目
http://127.0.0.1:端口號(hào)/open?projectpath=項(xiàng)目全路徑

如果你根本都沒(méi)有打開(kāi)微信開(kāi)發(fā)者工具,在以下地方就會(huì)找不到端口:

端口號(hào)文件位置:

macOS : ~/Library/Application Support/微信web開(kāi)發(fā)者工具/Default/.ide

Windows : ~/AppData/Local/微信web開(kāi)發(fā)者工具/User Data/Default/.ide

所以作為一個(gè)全自動(dòng)化打碼工具,怎么可能還要自己去手動(dòng)打開(kāi)微信開(kāi)發(fā)者工具呢!

前端

后端的東西基本就那么多,終于到前端了,前端十分簡(jiǎn)單,就不多說(shuō)了:

<template>
  <div>
    <group title="請(qǐng)選擇環(huán)境">
      <radio :options="envOption" v-model="env"></radio>
    </group>
    <x-button class="btn" type="default" @click.native="handlePreviewProject">點(diǎn)擊預(yù)覽</x-button>
    <div v-if="loginImg" class="code">
      <divider>請(qǐng)先登錄</divider>
      <img class="code-img" :src="loginImg" alt="">
    </div>
    <div v-if="preImg" class="code" id="preImg">
      <divider>預(yù)覽二維碼</divider>
      <img class="code-img" :src="`${base64Prefix}${preImg}`" alt="">
    </div>
  </div>
</template>

<script>
import {openProject, login, previewProject, buildProject} from 'SERVICES/index'
import {showLoading, hideLoading} from 'UTILS'
import { Divider, XButton, Radio, Group } from 'vux'
export default {
  data () {
    return {
      // data表示取得數(shù)據(jù)的協(xié)定名稱(chēng),image/png 是數(shù)據(jù)類(lèi)型名稱(chēng),base64 是數(shù)據(jù)的編碼方法,逗號(hào)后面就是這個(gè)image/png文件base64編碼后的數(shù)據(jù)。
      base64Prefix: 'data:image/png;base64,',
      // 登錄二維碼
      loginImg: '',
      // 預(yù)覽二維碼
      preImg: '',
      // 環(huán)境 默認(rèn)為doc
      env: 'doc',
      // 所有的環(huán)境選項(xiàng)
      envOption: ['doc', 'pre', 'prd']
    }
  },
  components: {
    Divider,
    XButton,
    Radio,
    Group
  },
  methods: {
    handleError (msg) {
      alert(msg)
    },
    async login () {
      const {data: {code, data, msg}} = await login()
      if (code !== 0) {
        this.handleError(msg)
        return code
      }
      this.loginImg = data
      return code
    },
    async previewProject () {
      const {data: {code, data, msg}} = await previewProject()
      if (code !== 0) {
        this.handleError(msg)
        return code
      }
      this.preImg = data
      return code
    },
    async handlePreviewProject () {
      showLoading()
      // 重置二維碼
      this.resetImg()
      // 打開(kāi)微信開(kāi)發(fā)者工具
      const {data: {code}} = await openProject()
      if (code !== 0) {
        // 登錄微信開(kāi)發(fā)者工具
        await this.login()
        hideLoading()
        return
      }
      // 根據(jù)環(huán)境打包
      await buildProject(this.env)
      // 預(yù)覽
      await this.previewProject()
      hideLoading()
    },
    resetImg () {
      this.loginImg = ''
      this.preImg = ''
    }
  }
}
</script>

<style lang='less'>
  .btn {
    width: 90%!important;
    margin: 30px auto 30px auto;
  }
  .code {
    display: flex;
    align-items: center;
    flex-direction: column;
    .code-img {
      width: 300px;
      height: 300px;
    }
  }
</style>

這里有一個(gè)坑就是,login返回的base64是帶了data:image/jpeg;base64,前綴的,所以可以直接放到img的src里,但是獲取預(yù)覽碼的preview返回的卻沒(méi)有這個(gè)前綴!所以需要自己加上去,就是那個(gè)base64Prefix:'data:image/png;base64,'

最后

其實(shí)到這里已經(jīng)基本實(shí)現(xiàn)了整個(gè)打碼功能,但如果真的要可以用還有很多事情沒(méi)做。

  1. 部署到測(cè)試機(jī)器上。雖然可以直接用自己的機(jī)子作為部署這個(gè)工具的機(jī)器,但這實(shí)在是有點(diǎn)……如果要部署到測(cè)試機(jī)器上,有一個(gè)問(wèn)題就是,微信開(kāi)發(fā)者工具依賴(lài)圖形界面,而服務(wù)器一般是命令行,雖然有這樣的項(xiàng)目移植微信開(kāi)發(fā)者工具到linux,但這種部署方式似乎還是怪怪的。
  2. 假設(shè)完成了上述部署,進(jìn)行小程序項(xiàng)目打包的環(huán)節(jié)需要修改一下,變成根據(jù)選擇的環(huán)境,到相應(yīng)的代碼倉(cāng)庫(kù)(比如gitlab)拉取該環(huán)境的最新代碼,然后進(jìn)行安裝依賴(lài)才能執(zhí)行打包命令。
  3. 既然都做到這一步了,也不差把上傳小程序也加上去,微信開(kāi)發(fā)者工具接口也有提供,這樣一來(lái)整個(gè)測(cè)試打碼到上線(xiàn)的步驟都有了。


易優(yōu)小程序(企業(yè)版)+靈活api+前后代碼開(kāi)源 碼云倉(cāng)庫(kù):starfork
本文地址:http://22321a.com/wxmini/doc/course/24643.html 復(fù)制鏈接 如需定制請(qǐng)聯(lián)系易優(yōu)客服咨詢(xún):800182392 點(diǎn)擊咨詢(xún)
QQ在線(xiàn)咨詢(xún)