• Small program name: taxi together
  • Project Address:

    Client: github.com/jrainlau/t….

    Server: github.com/jrainlau/t….

  • Small program QR code:

After a period of two evening work time efforts, finally put my first small program development completed and released online. The whole process is still smooth, because the MPVUE scheme is used for development, so you can enjoy the same smooth development experience with VUE; The background system uses Python3 +flask framework, using the least code to complete the small program’s background logic. In addition to development, I also really experienced the development process of a micro channel small program, including the use of developer tools, the release of the experience version, and the application for online and so on. All of these experiences are worth documenting, so I decided to write this article while the iron is hot.

I. Requirements & Functions

Since many colleagues in the company live in the same neighborhood, they often organize carpools in the company group for commuting. However, since we are totally dependent on chat history and have many colleagues carpooling to and from work, relying on group chat can easily erase messages and cause information confusion. In this case, it is perfectly possible to develop a small tool to solve these problems.

The initiator of carpooling will share the starting place, destination place and taxi information in the form of cards. By clicking the cards, the participants can choose to participate in carpooling, and see who the carpooling partner is, the information of the order and so on.

The interaction process is as follows:

As you can see, the logic is very simple, we just need to make sure that the four functions of creating a spell, sharing a spell, entering a spell, and exiting a spell are all right.

Requirements and functions have been determined, first of all, according to the introduction of the official website of the small program, register the small program and get the appId, then you can start the development of background logic.

Second, background logic development

Due to the short time and simple functions, we did not consider any complex scenarios such as high concurrency, but only the implementation of the functions. According to the logic of requirements, in fact, the background only needs to maintain two lists, which respectively store all current carpooling orders and all current users who participate in carpooling. Their data structure is as follows:

  • All current single column tablesbillsList

  • A list of all current carpool usersinBillUsers

When a user confirms and shares a pool, it will directly create a pool and add the user to the list of all current users participating in the pool and the list of members of the pool:

Once these two lists are maintained, the next step is the concrete business logic.

For quick development, I use python3+flask framework schemes here. For those of you who don’t know Python, don’t be alarmed. The code is very simple and straightforward, so take a look.

Start by creating a new BillController class:

class BillController:
    billsList = []
    inBillUsers = []Copy the code

Next, inside this class you will add the functions to create a quilt, get a quilt, participate in a quilt, exit a quilt, determine whether a user is in a quilt, and upload images.

1. Obtain the spell ordergetBill()

The method receives the spell ID from the client and retrieves the ID to see if a corresponding spell exists. If yes, the corresponding spell is returned. Otherwise, an error is reported to the client.

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': 'Spell does not exist! ', 'billsList': self.billsList, }, 1)Copy the code

2. Create a single ordercreateBill()

This method receives user information and spell information from the client and adds them to billsList and inBillUsers, respectively.

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': 'user is already in order! ' }, 1) self.billsList.append(bill) self.inBillUsers.append(user) return response({ 'billsList': self.billsList, 'inBillUsers': self.inBillUsers })Copy the code

Once created, the current billsList and inBillUsers are returned to the client.

3. Participate in orderingjoinBill()

Receives the user information and the spell ID from the client and adds the user to the spell and inBillUsers lists.

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': 'The user is already ordering! ' }, 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 })Copy the code

4. Exit the single orderleaveBill()

Receive the user ID and spell ID from the client, then delete the user in the two lists.

This function also has another function. If it is found that the member corresponding to the spell ID is empty, it will consider the spell as invalid and directly delete the spell and the corresponding vehicle information picture.

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) # Delete user 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 })Copy the code

5. Determine whether the user is in a single orderinBill()

After receiving the user ID sent by the client, inBillUsers will retrieve the corresponding spell of the user according to the user ID. If it can be retrieved, the spell it belongs to will be returned.

    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
        })Copy the code

6. Upload picturesuploadImg()

Receives the spell ID and picture resources from the client, stores the picture first, and then writes the path of the picture into the spell of the corresponding spell ID.

def uploadImg(self, ctx): billId = ctx.form['billId'] file = ctx.files['file'] filename = file.filename file.save(os.path.join('./imgs', 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 })Copy the code

Now that the business logic is functional, it’s time to distribute it to different routes:

@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/<filename>')
def getImg(filename):
  return send_from_directory('./imgs', filename)Copy the code

The complete code can be viewed directly in the repository, and only the key content is shown here.

Third, front-end business development

The front-end uses VUE-CLI directly to initialize the project using MPVUe-QuickStart of MPVue. The specific process will not be described in detail and will directly enter the business development part.

First of all, the API of wechat applet is callback style. For convenience of use, I packaged the API of the applet as Promise and put it inside SRC /utils/wx.js, similar to the following:

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) } }) })Copy the code

1. Register the global Store

Due to development habits, I like to put all interface requests in the Actions in the Store, so this little application also needs to use Vuex. Use (Vuex) does not register $store in the instance, because each Page is a new Vue instance.

Create a new store.js file under SRC/and register it for use:

import Vue from 'vue'
import Vuex from 'vuex'


Vue.use(Vuex)

export default new Vuex.Store({})Copy the code

Next, in SRC /main.js, manually register a $store in the Vue prototype:

import Vue from 'vue'
import App from './App'
import Store from './store'

Vue.prototype.$store = StoreCopy the code

In this way, you can use this.$store to manipulate the global store in any Page.

2. Build the requested API

Corresponding to the logic of the background system, the front end should also construct the API interface of each request, which can avoid the API logic scattered around the page, and has the advantage of being clear and easy to maintain.

/** * @param {} {commit} */ 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 userId * check whether the user already exists in a spell */ async checkInBill ({commit}, userId) { const res = await request({ method: 'post', url: `${apiDomain}/inBill`, data: { userId } }) return res }, /** * @param {} {commit} * @param {String} userId userId * @param {String} name user nickname * @param {String} avatar user avatar * @param {String} time Start time * @param {String} from start place * @param {String} to destination place * @param {String} billId ID * */ 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 Spell ID * get spell information */ async getBillInfo ({commit}, billId) { const res = await request({ method: 'post', url: `${apiDomain}/getBill`, data: { billId } }) return res }, /** * @param {} {commit} * @param {String} userId userId * @param {String} name user nickname * @param {String} avatar user avatar * @param {String} billId billId * joinBill */ 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 userId * @param {String} billId ID */ async leaveBill ({param {String}) commit }, { userId, billId }) { const res = await request({ method: 'post', url: `${apiDomain}/leave`, data: { userId, billId } }) return res }, /** * @param {} {commit} * @param {String} filePath path * @param {String} billId ID */ async uploadImg ({param {String} billId ID */ commit }, { filePath, billId }) { const res = await uploadFile({ url: `${apiDomain}/uploadImg`, header: { 'content-type': 'multipart/form-data' }, filePath, name: 'file', formData: { 'billId': billId } }) return res }Copy the code

3. Fill in the form and realize the sharing function

Create a SRC /pages/index directory as the home page of the applet.

The business logic of the home page is as follows:

  1. When entering the home page, obtain the user information first, and get the userId
  2. The request is then made with the userId to determine whether it is already in a spell order
  3. If yes, go to the details page of the corresponding spell Id
  4. If no, create a single bill

Implement the above logic in the onShow lifecycle hook:

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`) } },Copy the code

When the user has filled out the form, he clicks on a button with open-type=”share” and triggers the logic of the onShareAppMessage lifecycle hook to construct the form into a card to share. After the share is successful, the page for participating in a pool is displayed.

OnShareAppMessage (result) {let path = '/pages/index' if (result.from === 'button') {this.billid = 'billId-' + new Date().getTime() title = 'pages/join/main? BillId =${this.billid}'} return {title, path, success: async (res) => { await this.$store.dispatch('createBill', { ... this.userInfo, ... $store. Dispatch ('uploadImg', {filePath: this.imgsrc, billId: This.billid}) // After the share succeeds, it will jump to the join sheet page with billId. /join/main? billId=${this.billId}`) }, fail (e) { console.log(e) } } },Copy the code

4. Participate in the implementation of spelling & quit the spelling function

Create a SRC /pages/ Join directory as the “join sheet page” for the applet.

The page runs as follows:

  1. First we get the billId from the URL
  2. UserInfo is then requested once to get the userId
  3. The userId is then used to check whether the user is already in a spell order
  4. If it is already in a spell, a new billId is fetched instead of the one fetched from the URL
  5. Take the current billId to query the corresponding spell order information
  6. If billId doesn’t work, redirect to the home page

OnShow () is not available, but can only be obtained from onLoad() :

async onLoad (options) { // 1. BillId this. BillId = options.billId // 2. $store. Dispatch ('getUserInfo') // 3. Const inBill = await this.$store.dispatch('checkInBill', this.userinfo.userid) // 4. If (inbill.inbill) {this.billid = inbill.inbill.billid} // 5. If not, the current bill of billId will be requested // 6. If billId is invalid, redirect to home page, otherwise check if current user is in the spell await this.getbillInfo ()}Copy the code

In addition, when the user clicks “Participate in carpooling”, the user needs to request the information of carpooling again to refresh the list of view carpooling personnel; When the user clicks “Exit carpool”, it is redirected to the home page.

After the above steps, the client logic is complete and ready for pre-publishing.

Iv. Pre-release & online application

To release a pre-release, run the NPM run build command, pack a production package, and then upload the code through the applets developer tool’s upload button and fill in the test version number:

Then you can see the information of the small program in the small program management background → development management → development version, and then choose to release the experience version:

After confirming that the pre-release test is correct, you can click “Submit for Review” to formally submit the mini program to the wechat team for review. The audit time is very fast, within 3 hours can basically have a reply.

It is worth noting that all API requests for small programs must go through the domain name record and use HTTPS certificate, at the same time in the Settings → development Settings → server domain name API added to the whitelist can be used normally.

Five, the afterword.

This small program has now been released online, is a complete experience of a small program development fun. With the strong support of the wechat team, miniprogram will only prosper in the future. I had a bit of resistance to it when it came out, but then I thought, this is just another end of the line for front end engineers. There’s no need to be paranoid about it.

“Taxi bar” micro channel small program is still a toy like existence, only for their own learning and exploration, of course, also welcome readers can contribute code, participate in the development ~