需求開發(fā)小程序的朋友們隨時都會聽到一句話:“喂,快給我打一個xxx環(huán)境的預覽碼”,無論你正在干什么,都得趕緊地回一句:“稍等,這就給你打碼……” 然后苦逼的你build了一個xxx環(huán)境的包,打開了微信開發(fā)者工具,點了一下預覽,等了一下,預覽碼出來了,你復制丟給你的爸爸們。 終于有一天,你正在專心致志做一些不可描述的事情時,“喂,快給我打一個xxx環(huán)境的預覽碼”,這時你內心怒吼了一句:“老子不給你打碼!你自己打去!” 于是就有了這個需求,要搞個東西讓爸爸們自主打碼,嗯,應該就是只有一個按鈕,點一下就可以出現預覽二維碼的東西,意淫了一下應該是這樣的:
沒錯!就這樣干! 規(guī)劃一下干大事就要從胡思亂想開始,現在來想想要搞成這個功能,需要做點什么準備工作吧。
找微信開發(fā)者工具的接口
梳理開發(fā)流程 所需技術 工欲善其事,必先利其器,我們要搞這個東西,還是先要把用到的技術整理一下。
好像沒別的東西了,用到了再說吧。 擼起袖子從后端開始為了省事,直接把前后端的東西放在一起。項目目錄:
可以看到server這個目錄下放的都是后端的東西。
server/index.js
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)聽端口
app.listen(9871)
console.log(`[demo] start-quick is starting at port ${port}`)
靜態(tài)資源方面的話使用koa-static即可,重點是怎樣給前端提供接口,這就要看路由了。 server/router/index.js
const Router = require('koa-router')
// 業(yè)務邏輯
const wx = require('../controller/wx')
const router = new Router({
// 接口前綴 比如open接口 請求路徑就是/api/open
prefix: '/api'
})
router.get('/open', wx.open)
.get('/login', wx.login)
.get('/preview', wx.preview)
.get('/build', wx.build)
module.exports = router
這里可以清晰看到,后端提供了四個接口,但具體每個接口的業(yè)務邏輯則封裝在controller里的wx.js,如果以后還有別的業(yè)務邏輯,就在controller加相應的模塊即可。 server/controller/wx.js
// 微信開發(fā)者工具接口調用邏輯
const {open, login, preview, build} = require('../utli/wxToolApi')
// 處理成功失敗返回格式的工具
const {successBody, errorBody} = require('../utli')
class WxController {
/**
* 根據環(huán)境對mpvue項目進行打包
* @returns {Promise<void>}
*/
static async build (ctx) {
// 前端傳過來的get參數
const query = ctx.request.query
if (!query || !query.env) {
ctx.body = errorBody(null, '構建項目失敗')
return
}
const [err, data] = await build(query.env)
ctx.body = err ? errorBody(err, '構建項目失敗') : successBody(data, '構建項目成功')
}
/**
* 打開微信開發(fā)者工具
* @returns {Promise<void>}
*/
static async open (ctx) {
const [err, data] = await open()
ctx.body = err ? errorBody(err, '打開微信開發(fā)者工具失敗') : successBody(data, '打開微信開發(fā)者工具成功')
}
/**
* 登錄微信開發(fā)者工具
* @returns {Promise<void>}
*/
static async login (ctx) {
const [err, data] = await login()
ctx.body = err ? errorBody(err, '登錄二維碼返回失敗') : successBody(data, '登錄二維碼返回成功')
}
/**
* 查看預覽碼
* @returns {Promise<void>}
*/
static async preview (ctx) {
const [err, data] = await preview()
ctx.body = err ? errorBody(err, '預覽二維碼返回失敗') : successBody(data, '預覽二維碼返回成功')
}
}
module.exports = WxController
為了代碼更加清晰,這里將具體操作微信開發(fā)者工具的接口邏輯抽到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 = {
/**
* 根據環(huán)境對mpvue項目進行打包
* @param env [doc, pre, prd]
* @returns {*}
*/
build (env) {
return promiseWrap(new Promise((resolve, reject) => {
// 進入項目目錄
shell.cd(PROJECT_PATH)
// 執(zhí)行打包命令
shell.exec(`npm run build:${env}`, function (code, stdout, stderr) {
resolve(stdout)
})
}))
},
/**
* 打開微信開發(fā)者工具
* @returns {*}
*/
open () {
return promiseWrap(new Promise((resolve, reject) => {
// 進入項目目錄
shell.cd(INSTALL_PATH)
// 執(zhí)行微信開發(fā)者工具接口“命令行啟動工具”
shell.exec(`cli -o ${PROJECT_PATH}`, function (code, stdout, stderr) {
if (stderr) return reject(stderr)
resolve(stdout)
})
}))
},
/**
* 獲取微信開發(fā)者工具端口號
* @returns {Promise<*>}
*/
async getPort () {
shell.cd(PORT_PATH)
// http 服務在工具啟動后自動開啟,HTTP 服務端口號在用戶目錄下記錄,可通過檢查用戶目錄、檢查用戶目錄下是否有端口文件及嘗試連接來判斷工具是否安裝/啟動。
const [err, data] = await readFile(PORT_FILE_NAME)
return err ? errorBody(err, '讀取端口號文件失敗') : successBody(data, '讀取端口號文件成功')
},
/**
* 微信開發(fā)者工具進行登錄
* @returns {*}
*/
login () {
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}/login?format=base64`)
.then(res => {
resolve(res.data)
})
.catch(e => {
reject(e)
})
}))
},
/**
* 微信開發(fā)者工具獲取預覽碼
* @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)
})
}))
}
}
這里有一點需要注意,為什么只有open接口需要用命令行調用方式?那是因為HTTP調用方式必須加端口,比如open接口 # 打開工具 http://127.0.0.1:端口號/open # 打開/刷新項目 http://127.0.0.1:端口號/open?projectpath=項目全路徑 如果你根本都沒有打開微信開發(fā)者工具,在以下地方就會找不到端口: 端口號文件位置: macOS : ~/Library/Application Support/微信web開發(fā)者工具/Default/.ide Windows : ~/AppData/Local/微信web開發(fā)者工具/User Data/Default/.ide 所以作為一個全自動化打碼工具,怎么可能還要自己去手動打開微信開發(fā)者工具呢! 前端后端的東西基本就那么多,終于到前端了,前端十分簡單,就不多說了:
<template>
<div>
<group title="請選擇環(huán)境">
<radio :options="envOption" v-model="env"></radio>
</group>
<x-button class="btn" type="default" @click.native="handlePreviewProject">點擊預覽</x-button>
<div v-if="loginImg" class="code">
<divider>請先登錄</divider>
<img class="code-img" :src="loginImg" alt="">
</div>
<div v-if="preImg" class="code" id="preImg">
<divider>預覽二維碼</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表示取得數據的協定名稱,image/png 是數據類型名稱,base64 是數據的編碼方法,逗號后面就是這個image/png文件base64編碼后的數據。
base64Prefix: 'data:image/png;base64,',
// 登錄二維碼
loginImg: '',
// 預覽二維碼
preImg: '',
// 環(huán)境 默認為doc
env: 'doc',
// 所有的環(huán)境選項
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()
// 打開微信開發(fā)者工具
const {data: {code}} = await openProject()
if (code !== 0) {
// 登錄微信開發(fā)者工具
await this.login()
hideLoading()
return
}
// 根據環(huán)境打包
await buildProject(this.env)
// 預覽
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>
這里有一個坑就是,login返回的base64是帶了data:image/jpeg;base64,前綴的,所以可以直接放到img的src里,但是獲取預覽碼的preview返回的卻沒有這個前綴!所以需要自己加上去,就是那個base64Prefix:'data:image/png;base64,' 最后其實到這里已經基本實現了整個打碼功能,但如果真的要可以用還有很多事情沒做。
|