摘要:一起打車吧微信小程序依然是一個(gè)玩具般的存在,僅供自己學(xué)習(xí)和探索,當(dāng)然也歡迎各位讀者能夠貢獻(xiàn)代碼,參與開(kāi)發(fā)
小程序名稱:一起打車吧
項(xiàng)目地址:
客戶端:https://github.com/jrainlau/t...
服務(wù)端:https://github.com/jrainlau/t...
小程序二維碼:
經(jīng)過(guò)為期兩個(gè)晚上下班時(shí)間的努力,終于把我第一個(gè)小程序開(kāi)發(fā)完成并發(fā)布上線了。整個(gè)過(guò)程還算順利,由于使用了mpvue方案進(jìn)行開(kāi)發(fā),故可以享受和vue一致的流暢開(kāi)發(fā)體驗(yàn);后臺(tái)系統(tǒng)使用了python3+flask框架進(jìn)行,使用最少的代碼完成了小程序的后臺(tái)邏輯。除了開(kāi)發(fā)之外,還實(shí)實(shí)在在地體驗(yàn)了一把微信小程序的開(kāi)發(fā)流程,包括開(kāi)發(fā)者工具的使用、體驗(yàn)版的發(fā)布、上線的申請(qǐng)等等。這些開(kāi)發(fā)體驗(yàn)都非常值得被記錄下來(lái),于是便趁熱打鐵,寫(xiě)下這篇文章。
一、需求&功能由于公司里有相當(dāng)多的同事都住在同一個(gè)小區(qū),所以上下班的時(shí)候經(jīng)常會(huì)在公司群里組織拼車。但是由于完全依賴聊天記錄,且上下班拼車的同事也很多,依賴群聊很容易把消息刷走,而且容易造成信息錯(cuò)亂。既然如此,那么完全可以開(kāi)發(fā)一個(gè)小工具把這些問(wèn)題解決。
發(fā)起拼車的人把出發(fā)地點(diǎn)、目的地點(diǎn)、打車信息以卡片的形式分享出來(lái),參與拼車的人點(diǎn)擊卡片就能選擇參加拼車,并且能看到同車拼友是誰(shuí),拼單的信息等等內(nèi)容。
交互流程如下:
可以看到,邏輯是非常簡(jiǎn)單的,我們只需要保證生成拼單、分享拼單、進(jìn)入拼單和退出拼單這四個(gè)功能就好。
需求和功能已經(jīng)確定好,首先按照小程序官網(wǎng)的介紹,注冊(cè)好小程序并拿到appId,接下來(lái)可以開(kāi)始進(jìn)行后臺(tái)邏輯的開(kāi)發(fā)。
二、后臺(tái)邏輯開(kāi)發(fā)由于時(shí)間倉(cāng)促,功能又簡(jiǎn)單,所以并沒(méi)有考慮任何高并發(fā)等復(fù)雜場(chǎng)景,僅僅考慮功能的實(shí)現(xiàn)。從需求的邏輯可以知道,其實(shí)后臺(tái)只需要維護(hù)兩個(gè)列表,分別存儲(chǔ)當(dāng)前所有拼車單以及當(dāng)前所有參與了拼車的用戶即可,其數(shù)據(jù)結(jié)構(gòu)如下:
當(dāng)前所有拼單列表billsList
當(dāng)前所有參與了拼車的用戶列表inBillUsers
當(dāng)用戶確定并分享了一個(gè)拼單之后,會(huì)直接新建一個(gè)拼單,同時(shí)把該用戶添加到當(dāng)前所有參與了拼車的用戶列表列表里面,并且添加到該拼單的成員列表當(dāng)中:
只要維護(hù)好這兩個(gè)列表,接下來(lái)就是具體的業(yè)務(wù)邏輯了。
為了快速開(kāi)發(fā),這里我使用了python3+flask框架的方案。不懂python的讀者看到這里也不用緊張,代碼非常簡(jiǎn)單且直白,看看也無(wú)妨。
首先新建一個(gè)BillController類:
class BillController: billsList = [] inBillUsers = []
接下來(lái)會(huì)在這個(gè)類的內(nèi)部添加創(chuàng)建拼單、獲取拼單、參與拼單、退出拼單、判斷用戶是否在某一拼單中、圖片上傳的功能。
1、獲取拼單getBill()該方法接收客戶端傳來(lái)的拼單ID,然后拿這個(gè)ID去檢索是否存在對(duì)應(yīng)的拼單。若存在則返回對(duì)應(yīng)的拼單,否則報(bào)錯(cuò)給客戶端。
def getBill(self, ctx): ctxBody = ctx.form billId = ctxBody["billId"] try: return response([item for item in self.billsList if item["billId"] == billId][0]) except IndexError: return response({ "errMsg": "拼單不存在!", "billsList": self.billsList, }, 1)2、創(chuàng)建拼單createBill()
該方法會(huì)接收來(lái)自客戶端的用戶信息和拼單信息,分別添加到billsList和inBillUsers當(dāng)中。
def createBill(self, ctx): ctxBody = ctx.form user = { "userId": ctxBody["userId"], "billId": ctxBody["billId"], "name": ctxBody["name"], "avatar": ctxBody["avatar"] } bill = { "billId": ctxBody["billId"], "from": ctxBody["from"], "to": ctxBody["to"], "time": ctxBody["time"], "members": [user] } if ctxBody["userId"] in [item["userId"] for item in self.inBillUsers]: return response({ "errMsg": "用戶已經(jīng)在拼單中!" }, 1) self.billsList.append(bill) self.inBillUsers.append(user) return response({ "billsList": self.billsList, "inBillUsers": self.inBillUsers })
創(chuàng)建完成后,會(huì)返回當(dāng)前的billsList和inBillUsers到客戶端。
3、參與拼單joinBill()接收客戶端傳來(lái)的用戶信息和拼單ID,把用戶添加到拼單和inBillUsers列表中。
def joinBill(self, ctx): ctxBody = ctx.form billId = ctxBody["billId"] user = { "userId": ctxBody["userId"], "name": ctxBody["name"], "avatar": ctxBody["avatar"], "billId": ctxBody["billId"] } if ctxBody["userId"] in [item["userId"] for item in self.inBillUsers]: return response({ "errMsg": "用戶已經(jīng)在拼單中!" }, 1) theBill = [item for item in self.billsList if item["billId"] == billId] if not theBill: return response({ "errMsg": "拼單不存在" }, 1) theBill[0]["members"].append(user) self.inBillUsers.append(user) return response({ "billsList": self.billsList, "inBillUsers": self.inBillUsers })4、退出拼單leaveBill()
接收客戶端傳來(lái)的用戶ID和拼單ID,然后刪除掉兩個(gè)列表里面的該用戶。
這個(gè)函數(shù)還有一個(gè)功能,如果判斷到這個(gè)拼單ID所對(duì)應(yīng)的拼單成員為空,會(huì)認(rèn)為該拼單已經(jīng)作廢,會(huì)直接刪除掉這個(gè)拼單以及所對(duì)應(yīng)的車輛信息圖片。
def leaveBill(self, ctx): ctxBody = ctx.form billId = ctxBody["billId"] userId = ctxBody["userId"] indexOfUser = [i for i, member in enumerate(self.inBillUsers) if member["userId"] == userId][0] indexOfTheBill = [i for i, bill in enumerate(self.billsList) if bill["billId"] == billId][0] indexOfUserInBill = [i for i, member in enumerate(self.billsList[indexOfTheBill]["members"]) if member["userId"] == userId][0] # 刪除拼單里面的該用戶 self.billsList[indexOfTheBill]["members"].pop(indexOfUserInBill) # 刪除用戶列表里面的該用戶 self.inBillUsers.pop(indexOfUser) # 如果拼單里面用戶為空,則直接刪除這筆拼單 if len(self.billsList[indexOfTheBill]["members"]) == 0: imgPath = "./imgs/" + self.billsList[indexOfTheBill]["img"].split("/getImg")[1] if os.path.exists(imgPath): os.remove(imgPath) self.billsList.pop(indexOfTheBill) return response({ "billsList": self.billsList, "inBillUsers": self.inBillUsers })5、判斷用戶是否在某一拼單中inBill()
接收客戶端傳來(lái)的用戶ID,接下來(lái)會(huì)根據(jù)這個(gè)用戶ID去inBillUsers里面去檢索該用戶所對(duì)應(yīng)的拼單,如果能檢索到,會(huì)返回其所在的拼單。
def inBill(self, ctx): ctxBody = ctx.form userId = ctxBody["userId"] if ctxBody["userId"] in [item["userId"] for item in self.inBillUsers]: return response({ "inBill": [item for item in self.inBillUsers if ctxBody["userId"] == item["userId"]][0], "billsList": self.billsList, "inBillUsers": self.inBillUsers }) return response({ "inBill": False, "billsList": self.billsList, "inBillUsers": self.inBillUsers })6、圖片上傳uploadImg()
接收客戶端傳來(lái)的拼單ID和圖片資源,先存儲(chǔ)圖片,然后把該圖片的路徑寫(xiě)入對(duì)應(yīng)拼單ID的拼單當(dāng)中。
def uploadImg(self, ctx): billId = ctx.form["billId"] file = ctx.files["file"] filename = file.filename file.save(os.path.join("./imgs", filename)) # 把圖片信息掛載到對(duì)應(yīng)的拼單 indexOfTheBill = [i for i, bill in enumerate(self.billsList) if bill["billId"] == billId][0] self.billsList[indexOfTheBill]["img"] = url_for("getImg", filename=filename) return response({ "billsList": self.billsList })
完成了業(yè)務(wù)邏輯的功能,接下來(lái)就是把它們分發(fā)給不同的路由了:
@app.route("/create", methods = ["POST"]) def create(): return controller.createBill(request) @app.route("/join", methods = ["POST"]) def join(): return controller.joinBill(request) @app.route("/leave", methods = ["POST"]) def leave(): return controller.leaveBill(request) @app.route("/getBill", methods = ["POST"]) def getBill(): return controller.getBill(request) @app.route("/inBill", methods = ["POST"]) def inBill(): return controller.inBill(request) @app.route("/uploadImg", methods = ["POST"]) def uploadImg(): return controller.uploadImg(request) @app.route("/getImg/") def getImg(filename): return send_from_directory("./imgs", filename)
完整的代碼可以直接到倉(cāng)庫(kù)查看,這里僅展示關(guān)鍵的內(nèi)容。
三、前端業(yè)務(wù)開(kāi)發(fā)前端借助vue-cli直接使用了mpvue的mpvue-quickstart來(lái)初始化項(xiàng)目,具體過(guò)程不再細(xì)述,直接進(jìn)入業(yè)務(wù)開(kāi)發(fā)部分。
首先,微信小程序的API都是callback風(fēng)格,為了使用方便,我把用到的小程序API都包裝成了Promise,統(tǒng)一放在src/utils/wx.js內(nèi)部,類似下面這樣:
export const request = obj => new Promise((resolve, reject) => { wx.request({ url: obj.url, data: obj.data, header: { "content-type": "application/x-www-form-urlencoded", ...obj.header }, method: obj.method, success (res) { resolve(res.data.data) }, fail (e) { console.log(e) reject(e) } }) })1、注冊(cè)全局Store
由于開(kāi)發(fā)習(xí)慣,我喜歡把所有接口請(qǐng)求都放在store里面的actions當(dāng)中,所以這個(gè)小程序也是需要用到Vuex。但由于小程序每一個(gè)Page都是一個(gè)新的Vue實(shí)例,所以按照Vue的方式,用全局Vue.use(Vuex)是不會(huì)把$store注冊(cè)到實(shí)例當(dāng)中的,這一步要手動(dòng)來(lái)。
在src/目錄下新建一個(gè)store.js文件,然后在里面進(jìn)行使用注冊(cè):
import Vue from "vue" import Vuex from "vuex" Vue.use(Vuex) export default new Vuex.Store({})
接下來(lái)在src/main.js當(dāng)中,手動(dòng)在Vue的原型里注冊(cè)一個(gè)$store:
import Vue from "vue" import App from "./App" import Store from "./store" Vue.prototype.$store = Store
這樣,以后在任何的Page里都可以通過(guò)this.$store來(lái)操作這個(gè)全局Store了。
2、構(gòu)建好請(qǐng)求的API接口和后臺(tái)系統(tǒng)的邏輯對(duì)應(yīng),前端也要構(gòu)造好各個(gè)請(qǐng)求的API接口,這樣的做法能夠避免把API邏輯分散到頁(yè)面四處,具有清晰、易維護(hù)的優(yōu)勢(shì)。
/** * @param {} {commit} * 獲取用戶公開(kāi)信息 */ async getUserInfo ({ commit }) { const { userInfo } = await getUserInfo({ withCredenitals: false }) userInfo.avatar = userInfo.avatarUrl userInfo.name = userInfo.nickName userInfo.userId = encodeURIComponent(userInfo.nickName + userInfo.city + userInfo.gender + userInfo.country) commit("GET_USER_INFO", userInfo) return userInfo }, /** * @param {} {commit} * @param { String } userId 用戶ID * 檢查用戶是否已經(jīng)存在于某一拼單中 */ async checkInBill ({ commit }, userId) { const res = await request({ method: "post", url: `${apiDomain}/inBill`, data: { userId } }) return res }, /** * @param {} {commit} * @param { String } userId 用戶ID * @param { String } name 用戶昵稱 * @param { String } avatar 用戶頭像 * @param { String } time 出發(fā)時(shí)間 * @param { String } from 出發(fā)地點(diǎn) * @param { String } to 目的地點(diǎn) * @param { String } billId 拼單ID * 創(chuàng)建拼單 */ async createBill ({ commit }, { userId, name, avatar, time, from, to, billId }) { const res = await request({ method: "post", url: `${apiDomain}/create`, data: { userId, name, avatar, time, from, to, billId } }) commit("GET_BILL_INFO", res) return res }, /** * @param {} {commit} * @param { String } billId 拼單ID * 獲取拼單信息 */ async getBillInfo ({ commit }, billId) { const res = await request({ method: "post", url: `${apiDomain}/getBill`, data: { billId } }) return res }, /** * @param {} {commit} * @param { String } userId 用戶ID * @param { String } name 用戶昵稱 * @param { String } avatar 用戶頭像 * @param { String } billId 拼單ID * 參加拼單 */ async joinBill ({ commit }, { userId, name, avatar, billId }) { const res = await request({ method: "post", url: `${apiDomain}/join`, data: { userId, name, avatar, billId } }) return res }, /** * @param {} {commit} * @param { String } userId 用戶ID * @param { String } billId 拼單ID * 退出拼單 */ async leaveBill ({ commit }, { userId, billId }) { const res = await request({ method: "post", url: `${apiDomain}/leave`, data: { userId, billId } }) return res }, /** * @param {} {commit} * @param { String } filePath 圖片路徑 * @param { String } billId 拼單ID * 參加拼單 */ async uploadImg ({ commit }, { filePath, billId }) { const res = await uploadFile({ url: `${apiDomain}/uploadImg`, header: { "content-type": "multipart/form-data" }, filePath, name: "file", formData: { "billId": billId } }) return res }3、填寫(xiě)拼單并實(shí)現(xiàn)分享功能實(shí)現(xiàn)
新建一個(gè)src/pages/index目錄,作為小程序的首頁(yè)。
該首頁(yè)的業(yè)務(wù)邏輯如下:
進(jìn)入首頁(yè)的時(shí)候先獲取用戶信息,得到userId
然后用userId去請(qǐng)求判斷是否已經(jīng)處于拼單
若是,則跳轉(zhuǎn)到對(duì)應(yīng)拼單Id的詳情頁(yè)
若否,才允許新建拼單
在onShow的生命周期鉤子中實(shí)現(xiàn)上述邏輯:
async onShow () { this.userInfo = await this.$store.dispatch("getUserInfo") const inBill = await this.$store.dispatch("checkInBill", this.userInfo.userId) if (inBill.inBill) { wx.redirectTo(`../join/main?billId=${inBill.inBill.billId}&fromIndex=true`) } },
當(dāng)用戶填寫(xiě)完拼單后,會(huì)點(diǎn)擊一個(gè)帶有open-type="share"屬性的button,然后會(huì)觸發(fā)onShareAppMessage生命周期鉤子的邏輯把拼單構(gòu)造成卡片分享出去。當(dāng)分享成功后會(huì)跳轉(zhuǎn)到對(duì)應(yīng)拼單ID的參加拼單頁(yè)。
onShareAppMessage (result) { let title = "一起拼車" let path = "/pages/index" if (result.from === "button") { this.billId = "billId-" + new Date().getTime() title = "我發(fā)起了一個(gè)拼車" path = `pages/join/main?billId=${this.billId}` } return { title, path, success: async (res) => { await this.$store.dispatch("createBill", { ...this.userInfo, ...this.billInfo }) // 上傳圖片 await this.$store.dispatch("uploadImg", { filePath: this.imgSrc, billId: this.billId }) // 分享成功后,會(huì)帶著billId跳轉(zhuǎn)到參加拼單頁(yè) wx.redirectTo(`../join/main?billId=${this.billId}`) }, fail (e) { console.log(e) } } },4、參與拼單&退出拼單功能實(shí)現(xiàn)
新建一個(gè)src/pages/join目錄,作為小程序的“參加拼單頁(yè)”。
該頁(yè)面的運(yùn)行邏輯如下:
首先會(huì)獲取從url里面帶來(lái)的billId
其次會(huì)請(qǐng)求一次userInfo,獲取userId
然后拿這個(gè)userId去檢查該用戶是否已經(jīng)處于拼單
如果已經(jīng)處于拼單,那么就會(huì)獲取一個(gè)新的billId代替從url獲取的
拿當(dāng)前的billId去查詢對(duì)應(yīng)的拼單信息
如果billId都無(wú)效,則redirect到首頁(yè)
由于要獲取url攜帶的內(nèi)容,親測(cè)onShow()是不行的,只能在onLoad()里面獲?。?/p>
async onLoad (options) { // 1. 首先會(huì)獲取從url里面帶來(lái)的billId this.billId = options.billId // 2. 其次會(huì)請(qǐng)求一次userInfo,獲取userId this.userInfo = await this.$store.dispatch("getUserInfo") // 3. 然后拿這個(gè)userId去檢查該用戶是否已經(jīng)處于拼單 const inBill = await this.$store.dispatch("checkInBill", this.userInfo.userId) // 4. 如果已經(jīng)處于拼單,那么就會(huì)有一個(gè)billId if (inBill.inBill) { this.billId = inBill.inBill.billId } // 5. 如果沒(méi)有處于拼單,那么將請(qǐng)求當(dāng)前billId的拼單 // 6. 如果billId都無(wú)效,則redirect到首頁(yè),否則檢查當(dāng)前用戶是否處于該拼單當(dāng)中 await this.getBillInfo() }
此外,當(dāng)用戶點(diǎn)擊“參與拼車”后,需要重新請(qǐng)求拼單信息,以刷新視圖拼車人員列表;當(dāng)用戶點(diǎn)擊“退出拼車”后,要重定向到首頁(yè)。
經(jīng)過(guò)上面幾個(gè)步驟,客戶端的邏輯已經(jīng)完成,可以進(jìn)行預(yù)發(fā)布了。
四、預(yù)發(fā)布&申請(qǐng)上線如果要發(fā)布預(yù)發(fā)布版本,需要運(yùn)行npm run build命令,打包出一個(gè)生產(chǎn)版本的包,然后通過(guò)小程序開(kāi)發(fā)者工具的上傳按鈕上傳代碼,并填寫(xiě)測(cè)試版本號(hào):
接下來(lái)可以在小程序管理后臺(tái)→開(kāi)發(fā)管理→開(kāi)發(fā)版本當(dāng)中看到體驗(yàn)版小程序的信息,然后選擇發(fā)布體驗(yàn)版即可:
當(dāng)確定預(yù)發(fā)布測(cè)試無(wú)誤之后,就可以點(diǎn)擊“提交審核”,正式把小程序提交給微信團(tuán)隊(duì)進(jìn)行審核。審核的時(shí)間非???,在3小時(shí)內(nèi)基本都能夠有答復(fù)。
值得注意的是,小程序所有請(qǐng)求的API,都必須經(jīng)過(guò)域名備案和使用https證書(shū),同時(shí)要在設(shè)置→開(kāi)發(fā)設(shè)置→服務(wù)器域名里面把API添加到白名單才可以正常使用。
五、后記這個(gè)小程序現(xiàn)在已經(jīng)發(fā)布上線了,算是完整體驗(yàn)了一把小程序的開(kāi)發(fā)樂(lè)趣。小程序得到了微信團(tuán)隊(duì)的大力支持,以后的生態(tài)只會(huì)越來(lái)越繁榮。當(dāng)初小程序上線的時(shí)候我也對(duì)它有一些抵觸,但后來(lái)想了想,這只不過(guò)是前端工程師所需面對(duì)的又一個(gè)“端“而已,沒(méi)有必要為它戴上有色眼鏡,多掌握一些總是好的。
“一起打車吧”微信小程序依然是一個(gè)玩具般的存在,僅供自己學(xué)習(xí)和探索,當(dāng)然也歡迎各位讀者能夠貢獻(xiàn)代碼,參與開(kāi)發(fā)~
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/94442.html
摘要:一起打車吧微信小程序依然是一個(gè)玩具般的存在,僅供自己學(xué)習(xí)和探索,當(dāng)然也歡迎各位讀者能夠貢獻(xiàn)代碼,參與開(kāi)發(fā) 小程序名稱:一起打車吧 項(xiàng)目地址:客戶端:https://github.com/jrainlau/t... 服務(wù)端:https://github.com/jrainlau/t... 小程序二維碼:showImg(https://segmentfault.com/img/bV80...
摘要:用戶綁定的邏輯主要復(fù)雜在既需要考慮微信本身的接口在不同情況下提供的數(shù)據(jù)不同,另外一方面就是考慮本身用戶模塊的業(yè)務(wù)邏輯問(wèn)題。針對(duì)每一節(jié)課以及每一節(jié)系列課程生成小程序太陽(yáng)碼主要涉及到幾個(gè)細(xì)節(jié)問(wèn)題。 感覺(jué)已經(jīng)好久沒(méi)寫(xiě)程序了,最近這段時(shí)間,一方面是學(xué)習(xí)了python,然后折騰了scrapy框架,用python寫(xiě)了下守護(hù)進(jìn)程程序監(jiān)聽(tīng)任務(wù)以及用redis做隊(duì)列任務(wù)通信,并開(kāi)進(jìn)程來(lái)處理爬蟲(chóng)任務(wù)。以上...
摘要:整個(gè)小程序所有分包大小不超過(guò)單個(gè)分包主包大小不能超過(guò)微信小程序主流框架對(duì)比應(yīng)該算是最早發(fā)布的小程序開(kāi)發(fā)框架,提供了類的語(yǔ)法風(fēng)格和特性,現(xiàn)階段應(yīng)該也是應(yīng)用最廣泛的框架吧。不過(guò)微信官方為了防止下載離線包的時(shí)間過(guò)程,也嚴(yán)格限制了小程序包的體積。 那些年我們踩過(guò)的坑css樣式不能引用本地圖片資源,只能引用線上資源(background-image),引用本地圖片資源只能用標(biāo)簽。{{}}不能執(zhí)行...
摘要:根據(jù)官方文檔,用搭建腳手架。全局安裝創(chuàng)建一個(gè)基于模板的新項(xiàng)目安裝依賴啟動(dòng)構(gòu)建生成的目錄結(jié)構(gòu)如圖。示例圖小知識(shí)點(diǎn),標(biāo)簽?zāi)0寮瓤梢杂美镆部梢杂眯〕绦蚶锏?,比如等,在輪播圖中運(yùn)用方便高效。 1、根據(jù)官方文檔,用mpvue搭建腳手架。 # 全局安裝 vue-cli $ npm install --global vue-cli # 創(chuàng)建一個(gè)基于 mpvue-quickstart 模板的新項(xiàng)目 $...
閱讀 2365·2023-04-25 20:07
閱讀 3303·2021-11-25 09:43
閱讀 3662·2021-11-16 11:44
閱讀 2529·2021-11-08 13:14
閱讀 3178·2021-10-19 11:46
閱讀 895·2021-09-28 09:36
閱讀 2975·2021-09-22 10:56
閱讀 2374·2021-09-10 10:51