The original

I heard that 2018 will be a year of small program outbreak, perhaps with the current, as a front-end learner, I also began to play the small program, from the original in the nuggets to see other people write small program project, to now unconsciously their hands for more than a month, also want to do something to practice, so there is this small project.

Project introduction

This year, green orange bike was introduced to my city. It looks simple and fashionable, but I like it very much anyway. Just I’m in the study of small procedures, so I want to copy write a green orange bicycle to the front end of the small program implementation, big project was very cow force, many details of the experience of the ascension is worth learning, in which I also injected some own ideas, in general, I think small programs, such as Shared bikes actually experience also can do better.

In the course of this project, I stepped on a lot of pits, which is worth recording. Therefore, I will realize the process of various, as a share, I hope to help some students.

Simulated Cycle Refresh

Reset the location

Judge the nearest bike

Click on automatic bike path planning

Automatic segmentation of mobile phone number in input box

To simulate the login

Simulated code sweep cycling

Finish riding and pay

2018.6.13 Fixed the Bug where the mobile phone number was displayed as null

Github source code address: improved version of the qing 🍊 bike has the need to welcome fork, if you like, please give a Star


The specific content

The following is a detailed introduction

The directory structure

● ┣━ config # Mock ┣━ Images # ┣━ Libs # Amap SDK ┣━ Pages ● page port ━ init // main interface ━ login // login interface ━ UserCenter // personal center ┣━ messageCenter // messageCenter ┣━ unlock // unlock ┣━ charge // price ┣━ end // end of travel damage ━ repair // bicycle repair l ━ record // travel record ┣━ utils ┣━ app.js ┣━ app.wxss ┣━ app.json, l ━ project.config.jsonCopy the code

Build a harmonious and consistent map main interface

The Map component is one of the most complex components of the applets. It is a native component created by the client, and it is the highest level and cannot be controlled through the Z-Index hierarchy. This means that ordinary components cannot be overlaid on top of it. Except for the cover-view and cover-image components, which we’ll use next.

See the map component’s official documentation for more details

Using the elastic layout, arrange the map at the top and scan the unlock button at the bottom

According to actual measurement, the bottom button of Qingju bike is not a button component, but a View component, but some styles are added. The height of the bottom button is fixed, using the relative unit RPX. On different devices, the map component height should be automatically stretched or narrowed according to the device’s screen height without affecting the display effect. Using an elastic layout is the best solution, just set the button Flex: 1 at the bottom and adaptive at the top.

Use cover-Image to create harmonious controls

We can create controls using the Map component. You can also do this using a cover-image.

After experiencing other small programs of map class, I found that most of the map controls are made by using the Controls of the Map component. The controls have interactive effects of pressing, but they can only use pictures and cannot set styles, and their width and height unit is px by default. The actual experience is very strange on different devices.

Qorange uses The Cover-Image to create controls that cover the surface of the map, and sets the size of the controls by RPX relative units in the style, which can bring a pleasant effect. Not only that, but recently I found that the official document has been updated, and the cover-Image will completely replace controls

Make a shadow effect on the Map component

In the process of developing the first page of the map, the most impressive to me than this, after all, this deeply tortured me for a long period of time. Just to give you two random examples, let’s look at the effects of these little programs.

It doesn’t really matter if there’s a shadow effect or not, but maybe it’s the front end, and even if it’s implemented on the Map component, the front end developers have to figure out how to implement it, as Rebus said: because we’re engineers. If it’s only one percent better, we’re willing to give it 99 percent.

As mentioned earlier, the Map component is at the highest level. This is also the worst part. At first, I naivedly thought I could use CSS box-shadow property to fix it. The developer tools did show shadows, but when I tested them on the real machine, all shadows were overwritten by the Map component. There are only a few simple CSS styles supported by cover-View and cover-image, and it is almost impossible to create a shadow effect using CSS on a map component.

Currently there is only one solution, which is to use a cover-image to add an image that can be overridden on the map component to simulate shadows. As a matter of fact, these big factories all do so.

After analyzing the above problems, we can smoothly make the main interface effect, attach the home page WXML you will understand how to do, style specific implementation method can view my source code

<view class='map-box'>
  <map id='myMap' latitude='{{latitude}}' longitude='{{longitude}}' markers='{{markers}}' polyline='{{polyline}}' scale='{{scale}}' bindcontroltap='controltap' bindregionchange='regionchange' bindmarkertap='toVisit'show-location> <! --> <cover-image class= <cover-image class='map-shadow-top' src='/images/map-shadow-top.png'/>
      <cover-image class='map-shadow-btm' src='/images/map-shadow-btm.png'/ > <! --> <cover-view class='top-tips'>
        <cover-image class='top-icon' src='/images/top-tip.png'/>
        <cover-view class='top-text'>{{topText}}</cover-view> </cover-view> <! --> <cover-image class='map-icon_point' src='/images/point_in_map.png'/ > <! -- control --> <cover-image class='map-icon map-icon_msg' src='/images/icon-msg.png' bindtap='toMsg'/>
      <cover-image class='map-icon map-icon_user' src='/images/icon-user.png' bindtap='toUser'/>
      <cover-image class='map-icon map-icon_reset' src='/images/reset.png' bindtap='toReset'/>
  </map>
</view>
<view class='main-btn' bindtap='toScan'>
  <text class='main-text'</text> </view>Copy the code

Add location to the map

Small program for us to provide a lot of good API, development can go to view small program API

You can easily get the current location by calling the Wx.getLocation (OBJECT) API

wx.getLocation({
  type: 'gcj02',
  success: (res) => {
    let longitude = res.longitude;
    let latitude = res.latitude;
    this.setData({
      longitude,
      latitude
    })
})
Copy the code

Make the map interaction experience good

In the map applets, the most important interaction on the Map component is the reset position button

Repositioning is easy to implement by creating a map context and calling the moveToLocation()API

This.mapctx = wx.createmapContext ('myMap'); onReady() {this.mapctx = wx.createmapContext ('myMap'); }Copy the code

When using the Mobike mini app, if you zoom in and out of the map view, you have to manually zoom in and out of the map again every time you reposition because the bikes are in a pile

ToReset () {/ / back zoom ratio, ascending experience setTimeout (() = > {enclosing setData (18} {scale:)}, 1000). This mapCtx. MoveToLocation (); }Copy the code

This is also a small detail, map class small program can be used to achieve the following effect, this experience is very cool

Write a random function to generate fake bikes

In order to implement some more advanced features, I had to make some fake data to simulate a more realistic experience.

I simply wrote a method to randomly generate a batch of bikes attached to the coordinates of the current location.

Tocreate (res) {// Number of random bikes set here to 1-20 let ran = math.ceil (math.random () * 20); let markers = this.data.markers; for(let i = 0; i < ran; I++) {/ / define a temporary bicycle object var t_bic = {" id ": 0," title ":" go here, "iconPath" : PNG ", "callout":{}, "latitude": 0, "longitude": 0, "width": 52.5, "height": 30} var sign_a = math.random (); var sign_b = Math.random(); Var a = (math.ceil (math.random () * 99)) * 0.00002; Var b = (math.ceil (math.random () * 99)) * 0.00002; t_bic.id = i; T_bic. Longitude = (sign_a > 0.5? res.longitude + a : res.longitude - a); T_bic. Latitude = (sign_B > 0.5? res.latitude + b : res.latitude - b); markers.push(t_bic); } wx.setStorage({key: 'bicycle', data: markers}) this.setdata ({markers})}Copy the code

Then just call the fake bike function in the Map component’s Bindregionchange event

bindregionchange

Regionchange starting point (e) {/ / to get the longitude and latitude if (e. ype = = 'begin') {this. MapCtx. GetCenterLocation ({type: 'gcj02' success: (res) => { this.setData({ lastLongitude: res.longitude, lastLatitude: Res. Latitude})}})} / / get the current latitude and longitude if (e. ype = = 'end') {this. MapCtx. GetCenterLocation ({type: 'gcj02' success: (res) => { let lon_distance = res.longitude - this.data.lastLongitude; let lat_distance = res.latitude - this.data.lastLatitude; // console.log(lon_distance,lat_distance) // Determine how far the screen moves. Analog refresh bikes if (Math. Abs (lon_distance) > = 0.0035 | | math.h abs (lat_distance) > = 0.0022) {the console. The log (' refresh bikes') this. SetData ({/ / []}) this.tocreate(res)}})}}Copy the code

In this way, the following effect is achieved

Realize the function of judging the nearest bike

As you probably already know, of all the bikes on the map, the nearest bike has a little bubble on its head that’s closest to me. This is the function to find the latest bike, mobike has implemented this function, but qingju official did not add this little experience, it should be in the future. And I’m trying to do that here

Implementation logic

  1. Traverse the distance between each bike on the current map and the central coordinate point, and save it in an array

  2. Iterate through the array to find the smallest value and return the index of the smallest value

  3. Add a bubble prompt to the bike with the index of the minimum value

    NearestBic (res) {// Find the nearest bike let markers = this.data.markers; let min_index = 0; let distanceArr = []; For (let I = 0; i < markers.length; i++) { let lon = markers[i].longitude; let lat = markers[i].latitude; SQRT ((x1-x2)^2 + (y1-y2)^2) let t = math.sqrt ((lon-res.longitude) * (lon-res.longitude) + (lat - res.latitude) * (lat - res.latitude)); let distance = t; DistanceArr distanceArr. Push (distance)} let min = distanceArr[0]; for (let i = 0; i < distanceArr.length; i++) { if (parseFloat(distanceArr[i]) < parseFloat(min)) { min = distanceArr[i]; min_index = i; } } let callout = "markers[" + min_index + "].callout"; Wx. getStorage({key: 'bicycle', success: (res) => {this.setData({markers: res.data, [callout]) : {" content ": 'from me recently," color ":" # FFFFFF ", "fontSize" : "16" and "borderRadius" : "50", "padding" : "10", "bgColor" : "#0082FCaa", "display": 'ALWAYS' } }) } }) }Copy the code

Call this function every time the bike is refreshed and the map view changes, you can see the following effect, detailed call process please go to: source code

Manually select the bike automatically planning to walk to the path

B: well… This feature I think is still necessary, in some scenarios will encounter.

For example: I want to ride a bike, there is no car in sight.

Or if there is only one car, swipe the code on wechat and something terrible happens: the bike is temporarily unavailable.

I still want to ride a bike, do not want to walk, the map function will come into play, I will check the map of other bikes, at this time I saw some bikes, but have to walk a long way to find it, if you can click the bike, it will automatically plan the walking route.

So I took the plunge and made an implementation, as shown below

Now, how do you do that

It is almost impossible to implement automatic path planning by ourselves. We need to rely on the powerful power of the third party to achieve it.

Introduce the Amap SDK

First of all, I don’t know if you will think: What? Tencent map with gaode SDK inside?

This is not what can not, in the micro channel small program, whether Baidu Map, Autonavi Map, or Tencent map, small program specifically to provide Javascript SDK

Amap wechat small program SDK can help us get rich address description, POI and real-time weather data in the small program, as well as achieve address resolution and reverse address resolution and other functions, very powerful, but here we only need to use its path planning function

Amap wechat mini program SDK

Tencent Map and Baidu Map do not provide automatic path planning function for wechat mini program, so Autonavi is very considerate.

If you want to use it, you must go to the Amap open platform to register and obtain your key. The detailed steps are clearly introduced in the AMAP wechat mini program SDK guide

SDK Download address

Once you’ve downloaded it, unpack it and create a new liBS folder in your project directory to put it in

It is then introduced at the top of any JS file that needs to be used

var amapFile = require('.. /.. /libs/amap-wx.js'); Var myAmapFun = new amapfile.amapwx ({key: 'your key'});Copy the code

With this, you can write a method that takes care of regular paths

The route (bic) {/ / get the current center longitude and latitude enclosing mapCtx. GetCenterLocation ({success: (res) = > {/ / call Scott. Walk path planning map API myAmapFun getWalkingRoute ({origin: ` ${res. Longitude}, ${res. Latitude} `, destination: `${bic.longitude},${bic.latitude}`, success: (data) => { let points = []; if (data.paths && data.paths[0] && data.paths[0].steps) { let steps = data.paths[0].steps; for (let i = 0; i < steps.length; i++) { let poLen = steps[i].polyline.split('; '); for (let j = 0; j < poLen.length; j++) { points.push({ longitude: parseFloat(poLen[j].split(',')[0]), latitude: ParseFloat (poLen[j].split(',')[1])})}} this.setData({polyline: [{points: points, color: "#ffffffaa", arrowLine:true, borderColor: "#3CBCA3", borderWidth:2, width: 5, }] }); }})}})}Copy the code

The map component of wechat applets provides the polyline property, which can draw a path based on a set point above the map component

Path color and style can be set, wow ~ is a bit cool

Here, in honor of Green Orange bicycle, I try to make the style of the path like green Orange bicycle 😀, and then we will review the effect

Create a good login interface

Now that the main interface of the map is done, let’s write the login interface.

Login page seems simple, but want to make a good experience of the login interface, the actual implementation, there is a lot of logic

Automatic segmentation of mobile phone numbers

In the small program of Qingju Bike, I found such a small detail. The mobile phone number entered in the input box will be automatically divided, which feels like a good user experience. The phone number divided and displayed can make the mistakes in the input process clear and look more cool

In order to mimic the experience, I implemented it in my own logic.

My implementation logic

  1. Mobile phone numbers are 11 digits, divided into three sections XXX space XXXX space XXXX, we in the third input and the seventh input after the number of additional space, it can not achieve this effect

  2. Because two Spaces have been added, set the maximum length of the input field to 13 bits

  3. The value attribute of the input is bound to the phoneText defined in the data logical layer, and then you can use JS to change its display importance!!

  4. Set the bindinput property so that the input function is executed for each input

    <input class='input' placeholder=' maxLength ="13" value='{{phoneText}}' bindinput='input'/>

I wrote this input method to separate the phone numbers

input(e) { let value = e.detail.value; / / regular filter value = value. The replace (/ [\ u4E00 - \ u9FA5 ` ~! @ # $% ^ & * () _ + < >? : ", {}, \ /; '\ [\] - \ sa - zA - Z] * / g, ""); let result = []; for (let i = 0; i < value.length; i++) { if (i == 3 || i == 7) { result.push(" ", value.charAt(i)); } else { result.push(value.charAt(i)); } } this.setData({ phoneText: result.join("") }) }Copy the code

Note: In order to achieve this effect, however, some compromises have been made, that is, wechat’s built-in numeric keypad cannot be called for input. Otherwise you won’t see the effect of the partition

In fact, the built-in keyboard of wechat can be very convenient to avoid the input of illegal characters, that is, characters other than numbers, such as English letters, punctuation marks and so on.

Button available & not available logic

Before the basic input box is completed, the button should be unavailable and the verification code input box should be hidden. After the user fills in the verification code input box, the button will appear and light up after the verification code is entered. This setting should be more in line with the user’s psychological suggestion

This page involves two buttons to get a captcha as well as a next step and a clear input block diagram

  • The mobile phone number input box should not be able to enter characters other than numbers, although there is certainly no XSS, but for the sake of rigor, use the re filter
  • When there is content in the input box, the clear content button appears. After the content is cleared, the clear content button disappears, and all buttons are unavailable
  • After the mobile phone number is filled in, the obtain verification code button becomes available and the verification code input box appears
  • If the mobile phone number and verification code meet the requirements, the next button becomes available
  • When you click Next, or get a verification code, verify that your phone number is correct

The implementation effect is as follows, the specific implementation code please move to the source code

Scan code unlock function

To realize scanning unlock, you only need to call the API of wx.scancode () of the small program to call the scanning function of the camera. Of course, before scanning, login check is carried out first. If you do not log in, switch to the login interface

toScan(){ if (! App. GlobalData. LoginStatus) {wx. ShowModal ({title: 'prompt', content: 'please login first, success: (res) => { if (res.confirm) { wx.navigateTo({ url: '/pages/login/login' }) } } }) } else { wx.scanCode({ success: (res) => {onlyFromCamera: false, console.log(' Scan succeeded '); wx.navigateTo({ url: '/pages/unlock/unlock', }) } }) } }Copy the code

After unlocking, enter the cycling state, and the effect is as follows:

In the riding state, only the current riding vehicle is displayed, and bubbles are added above the vehicle to indicate that the riding is in progress

Riding the billing

The charging of shared bikes is judged according to the usage Time. Since there is no back-end data, only one timer method Time() can be written here to simulate the charging

Time(){ let s = 0; This.timer = setInterval(() => {this.setdata ({second: s++}) if (s == 60) {s = 0; m++; setTimeout(() => { this.setData({ minute: m }); }}, 1000); }}, 1000)Copy the code

When the ride starts, call the timer, start the time, click the end of the ride, the timer will stop, charge according to the time, and jump to the payment page

conclusion

Due to the short period of time, some functions of the project have not been added, and some areas need to be improved. I will spare time to polish them gradually later. If you have better ideas, you can also contact me to improve them.

Finally, attached again is my project address: improved Version of Qing 🍊 Bike

If you like, don’t be stingy with your Star!

The growth cabin of Mowgli: A “modified Version of qing 🍊 bike”