Small micro-channel program - run it

Foreword

Learning (into the pit) has wanted to come to the front end of three months, learning is always boring, write code even more so. However, anyone remember? In high school you can because the solution out of a troubled math problem a few days you happy all day. In the world of programmers, I think, no more happy than to make a project of the.

Yes, and after a small micro-channel learning program for nearly a month, imitation of the APP- sports world campus wrote a letter of micro-small programs run it (named because this is the first time to see the thought of running run, brother) . In fact, I feel really write this project can not be regarded as a project, because it only achieved a fraction of the original function of APP, it is simple to write, in order to perfect, really still have a team to write. Well, he did not talk much, we get down to business.

First posted here a github address: run it inside all the source code.

Project Showcase

Before using development tools and ready

  • Micro letter Developer Tools + VScode
  • Vant-weapp+Tencent/weui-wxss
  • iconfont + pictures online resources
  • Applet cloud database development, storage, and cloud function
  • Micro letter applet official document + Vant official document + weui official documents
  • Netease cloud music NodeJS API version

Project structure

General structure

Page path, show window of the page, the bottom of the tab

"pages": [
    "pages/run/run",
    "pages/map/map",
    "pages/chat/chat",
    "pages/mine/mine",
    "pages/music/music",
    "pages/share/share",
    "pages/play/play"
  ],
  "window": {
    "backgroundColor": "#F6F6F6",
    "backgroundTextStyle": "light",
    "navigationBarBackgroundColor": "#00B26A",
    "navigationBarTitleText": "奔跑吧",
    "navigationBarTextStyle": "white"
  },
  "tabBar": {
    "color": "#B5B5B5",
    "selectedColor": "#1296DB",
    "list": [
      {
        "text": "运动",
        "pagePath": "pages/run/run",
        "iconPath": "images/run.png",
        "selectedIconPath": "images/run-active.png"
      },
      {
        "text": "动态",
        "pagePath": "pages/chat/chat",
        "iconPath": "images/chat.png",
        "selectedIconPath": "images/chat-active.png"
      },
      {
        "text": "我的",
        "pagePath": "pages/mine/mine",
        "iconPath": "images/mine.png",
        "selectedIconPath": "images/mine-active.png"
      }
    ]
  },
复制代码

I have to say tabBar small micro-channel program is really easy to use, without writing js logic can be easily achieved.

mine

I will start with the easiest to begin with it, I almost did not write any page js (except acquisition has been running mileage data), are just cut page. ( Note: Because the code is too big, I am not one posted elaborated, will talk about the key points described below tutte, want to see can be posted to all of the above code github URL point of view )

Here is my head open data directly with the micro-channel program provides a small open-data labels directly shows the micro-channel avatars and nicknames than Button to use is not simple.

<view class="header">
        <view class="left">
            <open-data type="userAvatarUrl" class="left-ava"></open-data>
        </view>
        <view class="mid">
            <view class="mid-top">
                <open-data type="userNickName"></open-data>
            </view>
            <view class="mid-bottom">东华理工大学南昌广兰校区 软件学院</view>
        </view>
        <view class="right">
            <view class="arrow"></view>
        </view>
    </view>
复制代码

Needless to say that the arrow on the right, is a simple css.

.right .arrow{
    width: 30rpx;
    height: 30rpx;
    border-top: 1px solid #fff;
    border-right: 1px solid #fff;
    transform-origin: 0 0;
    transform: rotate(45deg);
}
复制代码

Then we have to look below the head next to that part, where the use of elastic layout and involves 1px problem.

<view class="hd-footer">
    <view class="ft-left">
        <view class="num">120.00</view>
        <view class="str">
            <text>学期目标</text>
        </view>
    </view>
    <view class="ft-mid">
        <view class="num">{{num}}</view>
        <view class="str">
            <text>已跑里程</text>
        </view>
    </view>
    <view class="ft-right">
        <view class="num">0.00</view>
        <view class="str">
            <text>计入成绩</text>
        </view>
    </view>
</view>    
复制代码
.hd-footer{
    display: flex;
    padding: 30rpx;
    background-color: #fff;
    text-align: center;
    font-size:14px;
}
.ft-left{
    flex: 1;
    position: relative;
}
.ft-left:after{
    content:"";
    position: absolute;
    top: 0;left: 0;
    width: 200%;
    height: 200%;
    box-sizing: border-box;
    transform: scale(0.5);
    transform-origin: 0 0;
    border-right: 1px solid #aaa;
}
.ft-mid{
    flex: 1;
    position: relative;
}
.ft-mid:after{
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 200%;
    height: 200%;
    box-sizing: border-box;
    transform: scale(0.5);
    transform-origin: 0 0;
    border-right: 1px solid #aaa;
}
.ft-right{
    flex: 1;
}
复制代码

To a parent container is provided .hd-footer display: flex; to its three sub-containers to flex: 1; each third of the sub-containers parent container. We know that border can only be set to a minimum 1px, but we can achieve 0.5px by adding a pseudo-element, which is a small tip of css.

The following body portions of the resilient arrangement is also used, not repeat them here. .footer part of the bottom cell is the use of Cell Vant component assembly.

{
  "usingComponents": {
    "van-cell": "../vant-weapp/dist/cell/index",
    "van-cell-group": "../vant-weapp/dist/cell-group/index"
  }
}
复制代码
<view class="footer">
    <van-cell-group>
        <van-cell title="联系客服" icon="setting-o" is-link url=""link-type="navigateTo"/>
        <van-cell title="设置" icon="service-o" is-link border="true"url="" link-type="navigateTo"/>
    </van-cell-group>
</view>
复制代码

Finally, talk about js here only part of it, {{num}} using MVVM (data-driven page) thought, set the value of the global sum of the data after a complete run through, and then get the value of the global sum of the data in this screen and give this page data num, and finally to render the page. Mentioned MVVM have to praise it, it is really a great creation, so that we can blows tedious DOM manipulation.

    this.globalData = {
      sum: '0.00',
      baseUrl: 'http://neteasecloudmusicapi.zhaoboy.com'
    }
复制代码
    this.setData({
      num: app.globalData.sum
    })
复制代码

dynamic

js logic of the interface more involved, I will respect js speaker.

The head is still using elastic layout, text mention here beyond the ellipsis to play it.

.hobby-title{
    font-size: 14px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
复制代码

Body parts have a look at many of the same structure, so here I wrote a speak assembly, structure and style components have nothing to say, they talk about how the three data (time, text, images) on the components. It will talk about this by position: fixed; to locate the button, when clicked, jump to share the page.

    <view class="add" bindtap='share'>
        <image class="add-btn" src="../../images/add.png"></image>
    </view>
复制代码
  share() {
    wx.navigateTo({
      url: '../share/share',
    })
  },
复制代码

Here achieve the jump page is quite simple.

A Button, a input box and weui the Uploader upload component.

<view class="write">
  <button type='primary' size='mini' class='btn' bindtap='send'>发布</button>
  <input type='text' placeholder='分享...' class='towrite' bindconfirm='complete'></input>
  <view class="page__bd">
    <view class="weui-cells">
      <view class="weui-cell">
        <view class="weui-cell__bd">
          <view class="weui-uploader">
            <view class="weui-uploader__hd">
              <view class="weui-uploader__title">图片上传</view>
              <view class="weui-uploader__info">{{files.length}}/2</view>
            </view>
            <view class="weui-uploader__bd">
              <view class="weui-uploader__files" id="uploaderFiles">
                <block wx:for="{{files}}" wx:key="*this">
                  <view class="weui-uploader__file" bindtap="previewImage" id="{{item}}">
                    <image class="weui-uploader__img" src="{{item}}" mode="aspectFill" />
                  </view>
                </block>
              </view>
              <view class="weui-uploader__input-box">
                <view class="weui-uploader__input" bindtap="chooseImage"></view>
              </view>
            </view>
          </view>
        </view>
      </view>
    </view>
  </view>
</view>
复制代码

Weui introduced in app.wxss in style.

@import './weui.wxss';
复制代码
data: {
    files: [],
    fileID: [],
    content: ''
  },
  chooseImage() {
    let that = this;
    wx.chooseImage({
      sizeType: ['original','compressed'],
      sourceType: ['album','camera'],
      success(res) {
        console.log(res);
        that.setData({
          files: that.data.files.concat(res.tempFilePaths)
        })
        // ------
        for(let i = 0; i < res.tempFilePaths.length; i++) {
          const filePath = res.tempFilePaths[i];
          let randString = Math.floor(Math.random() * 1000000).toString() + filePath.match(/\.[^.]+?$/);
          wx.cloud.uploadFile({
            cloudPath:randString,
            filePath,
            success: res => {
              console.log('上传成功',res);
              that.data.fileID.push(res.fileID);
            },
            fail: err => {
              console.log(err);
            }
          })
        }
      }
    })
  },
  previewImage(e) { 
    console.log(e);
    wx.previewImage({
      current: e.currentTarget.id,
      urls: this.data.files
    })
  },
  complete(e) {
    this.setData({
      content: e.detail.value
    })
  },
  send() {
    wx.cloud.callFunction({
      name: 'createDynamic',
      data: {
        content: this.data.content,
        imagePath: this.data.fileID
      },
      success(res) {
        console.log(res.result);
        wx.navigateBack();
      },
      fail(error) {
        console.log(error);
      }
    })
  },
复制代码

This uses a few small micro-channel API program. To set the data in the data (image path) through the success wx.chooseImage callback. With wx.cloud.uploadFile pictures uploaded to the cloud resource development in because you can only upload one, so here with the for loop. You can preview the picture by wx.previewImage. What is the meaning of each particular parameter, you can go to the official documentation point of view.

= 'Complete' acquires the input content input box bindconfirm by the input, the final function is invoked by the send method createDynamic cloud Button button binding.

// 云函数入口文件
const cloud = require('wx-server-sdk')
const env = 'lvwei666-pv2y1'

cloud.init()
const db = cloud.database({env})
// 云函数入口函数
exports.main = async (event, context) => {
    const userInfo = event.userInfo;
    return await db.collection('dynamic').add({
      data: {
        content: event.content,
        images: event.imagePath,
        createBy: userInfo.openId,
        createTime: new Date()
      }
    })
}
复制代码

createDynamic cloud coming when you call the function to get the content and images path and add this one time data, these three data storage to the dynamic of this database. (Here really want to Tucao about cloud commissioning function, once the error, every time debugging function should upload a cloud, very time-consuming and cumbersome.)

Data created, we go get the data.

const db = wx.cloud.database()
const dynamic = db.collection('dynamic')
复制代码
onShow: function () {
    let self = this;
    wx.showLoading({
      title: '正在加载中'
    });
    dynamic.get({
      success(res) {
        let every = res.data.reverse()
        for (let n of every) {
          n.createTime = n.createTime.getFullYear() + '-' + (+n.createTime.getMonth() + 1) + '-' + n.createTime.getDate() + ' ' + n.createTime.getHours() + ':' + n.createTime.getMinutes() + ':' + n.createTime.getSeconds();
        }
        self.setData({
          every
        })
      },
      fail(error) {
        console.log(error);
      },
      complete() {
        wx.hideLoading();
      }
    })
  },
复制代码

In fact, onPullDownRefresh page related event handlers - monitor user actions in the drop-down and I put the same code can also obtain data when the drop-down refresh.

Get the data array I had two operations, one to make the array reverse it because the newly added record (dynamic) there will be the last dynamic database; the second is the time of this data string concatenation operations to a meal the way to get what you want.

Then we talked more body part of it dynamic pages, that is, speak assembly. In this first page introducing speak components.

    <view class="body">
      <block wx:for="{{every}}" wx:key="index">
        <speak createTime="{{item.createTime}}" content="{{item.content}}" images="{{item.images}}"></speak>
      </block>
    </view>
复制代码
  "usingComponents": {
    "speak": "../../components/speak/speak"
  },
复制代码

There is also used for loop to render speak assembly, because dynamic collection where you can add multiple records, a component to render a record speak. Components and how to get the data on the page it? We look at the following code will know.

  properties: {
    createTime: {
      type: String,
      value: ''
    },
    content: {
      type: String,
      value: ''
    },
    images: {
      type: Array,
      value: []
    }
  },
复制代码

Property list properties components - this allows the components to get data from a page there. With the data is in html digging directly ({}), the data put the herd.

<view class="item">
  <view class="header">
    <view class="left">
      <open-data type="userAvatarUrl"></open-data>
    </view>
    <view class="right">
      <open-data class="right-top" type="userNickName"></open-data>
      <view class="right-bottom">{{createTime}}</view>
    </view>
  </view>
  <view class="body">
    <view class="content">{{content}}</view>
        <view class="img" wx:for="{{images}}" wx:key="index" bindtap="previewImage" id='{{item}}'>
          <image src="{{item}}" mode="aspectFill" alt="" />
        </view>
  </view>
  <view class="footer">
    <view class="click">
      <view class="click-left">
        <image class="comment" src='../../images/comment.png'></image>
        <text class="comment-num">0</text>
      </view>
      <view class="click-right">
        <image class="support" src="{{like ? '../../images/support.png' : '../../images/support-active.png'}}" bindtap='like'></image>
        <text class="support-num">{{num}}</text>
      </view>
    </view>
  </view>
</view>
复制代码
  data: {
    like: true,
    num : 0
  },
  methods: {
    like() {
      if (this.data.like) {
        this.setData({
          num: this.data.num + 1
        })
      }else {
        this.setData({
          num: this.data.num - 1
        })
      }
      this.setData({
        like: !this.data.like
      })
    },
    previewImage(e) {
      // console.log(e);
      wx.previewImage({
        current: e.currentTarget.id,
        urls: this.properties.images
      })
    }
  }
复制代码

Controlling a data like true or false by a method like cancellation point and reaches the point Like Like effects wx.previewImage here used to preview images.

motion

Here is not a map, we can show to see the effect according to what I said above project. The first page you can see is a sliding screen effect, using Vant's van-tab component, the title and content of each screen of my existence navData database. Click the pop-term target of a similar pop-up menu with a Vant's van-action-sheet components. A reference to the data acquisition and operation of the database in the foregoing components are mentioned, not talk about it here.

Css animation talk about the middle of the Start button, right, compared to the original APP in effect, that I was really a lot of difference, in the original APP looks like water droplets as smooth.

.anim{
    width: 250rpx;
    height: 250rpx;
    background-color: white;
    opacity: 0.3;
    border-radius: 50%;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%,-50%);
    transform-origin: 0 0;
    animation: expend 2.5s ease-in-out both infinite;
}
@keyframes expend{
    0% {
        opacity: 0;
        transform: scale(1) translate(-50%,-50%);
    }
    40% {
        opacity: 0.2;
        transform: scale(1.7) translate(-50%,-50%);
    }
    100% {
        opacity: 0;
        transform: scale(1.7) translate(-50%,-50%);
    }
}
.start{
    width: 250rpx;
    height: 250rpx;
    background-color: white;
    border-radius: 50%;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%,-50%);
    text-align: center;
    line-height: 250rpx;
    color: #7AD5C7;
    transform-origin: 0 0;
    animation: dd 2.5s linear both infinite;
}
@keyframes dd {
    0%,8%,100%{
        transform: translate(-50%,-50%) scale(1);
    }
    5% {
        transform: translate(-50%,-50%) scale(.98);
    }
}
复制代码

.anim enlarged side is the side to change its transparency, and .start on the first shrink a little, then immediately restored to their original.

Then talk about the music part, the music button on the page to enter the music page.

wx.request({
      url: 'http://neteasecloudmusicapi.zhaoboy.com/top/list',
      data: {
        idx: 1
      },
      success: res => {
        console.log('热歌', res);
        const songLists = res.data.playlist.tracks;
        this.setData({
          songLists
        });
        wx.hideLoading();
      }
    })
复制代码

Here initiate a request by wx.request to get NetEase cloud music hot music and hot song list and interface information for loop through the data to render the page, talk here about the data in the idx: 1 What is that (Note: Call this interface, incoming digital idx, you can get different rankings) this is the official explanation, 0 represents the song list, 1 for hot song list, and so on.

    <view class='songlist'>
      <block wx:for="{{songLists}}" wx:key="index">
        <view class='item' data-id="{{item.id}}" bindtap='toPlayAudio'>
          <view class='index'>{{index + 1}}</view>
          <view class='rightView'>
            <view class='songTitle'>{{item.name}}</view>
          </view>
        </view>
      </block>
    </view>
复制代码
  toPlayAudio(e) {
    const id = e.currentTarget.dataset.id;
    wx.navigateTo({
      url: `../play/play?id=${id}`
    })
  },
复制代码

Note that each .item has data-id = "{{item.id}}", which is to wait for the next jump know what song is playing when the page to play music.

Interface and style of play are simple pages, we'll look at how to achieve it play music.

onLoad: function (options) {
    console.log(options);
    wx.setNavigationBarTitle({
      title: '云音乐',
    })
    wx.setNavigationBarColor({
      frontColor: '#ffffff',
      backgroundColor: '#3daed9',
    })
    const id = options.id
    wx.request({
      url: app.globalData.baseUrl + '/song/url',
      data: {
        id: id
      },
      success: res => {
        console.log('歌曲详情', res);
        if (res.data.code === 200) {
          this.createBackgroundAudio(res.data.data[0] || {});
        }
      }
    })

    wx.request({
      url: app.globalData.baseUrl + '/song/detail',
      data: {
        ids: id
      },
      success: (res) => {
        console.log('歌曲信息', res);
        this.setData({
          song: res.data.songs[0]
        })
      }
    })
  },
  createBackgroundAudio(songInfo) {
    const bgAudio = wx.getBackgroundAudioManager();
    bgAudio.title = "title";
    bgAudio.epname = "epname";
    bgAudio.singer = "chris wu";
    bgAudio.coverImgUrl = "";
    bgAudio.src = songInfo.url;
    bgAudio.onPlay(res => {
      this.setData({
        isPlay: true
      })
    })
  },
复制代码

Can be passed in time to get the jump page through options id, then Url wx.request get songs and calls wx.getBackgroundAudioManager background audio player managed to play music, but also with wx.request get the song details.

    <view>
      <button type='primary' bindtap='togglePlayStatus'>
      {{isPlay ? '暂停' : '播放'}}
      </button>
    </view>
复制代码
  togglePlayStatus() {
    const bgAu = wx.getBackgroundAudioManager();
    if (this.data.isPlay) {
      bgAu.pause();
      this.setData({
        isPlay: false
      })
    } else {
      bgAu.play();
      this.setData({
        isPlay: true
      })
    }
  },
复制代码

By togglePlayStatus method to control playback and pause music.

Finally, this is the running part, which is calculated from the motion logic can be said that the entire project in the most difficult places. Amended many times, before it can be rough to calculate the number of kilometers run, and I also shed a lot of sweat.

First introduced Tencent map.

<map id='myMap' scale='{{scale}}' latitude='{{latitude}}' longitude='{{longitude}}' polyline="{{polyline}}" show-location markers='{{markers}}'></map>
复制代码

Zoom level of the map scale, latitude and longitude, polyline route, show-location shows the current anchor point with direction, markers markers of these properties not explained in detail, interested can go to the official documents and see.

 onLoad: function(options) {
    let markers = [];
    let marker = {
      iconPath: "../../images/baseline.png",
      id: 0,
      width: 40,
      height: 40
    };
    wx.getLocation({
      type: 'gcj02',
      success: (res) => {
        console.log(res)
        marker.latitude = res.latitude;
        marker.longitude = res.longitude;
        markers.push(marker)
        this.setData({
          latitude: res.latitude,
          longitude: res.longitude,
          markers
        })      
      },
      fail: (error) => {
        console.log(error);
        wx.showToast({
          title: '获取地理位置失败',
          icon: 'none'
        })
      }
    })
  },
    onReady() {
    this.mapCtx = wx.createMapContext('myMap');
    this.start();
  },
复制代码

When the page is loaded on to get the current location as a starting point and add a marker, call the start function when the page is first rendered complete by wx.getLocation, we'll start to see what has been done.

start() {
    let that = this;
    this.timer = setInterval(repeat, 1000);
    function repeat() {
      console.log('re');
      that.getLocation();
      that.drawLine();
    }
    cal = setInterval(() => {
      let dis, sum = 0;
      for (let i = 0; i < point.length - 1; i++) {
        dis = that.getDistance(point[i].latitude, point[i].longitude, point[i + 1].latitude, point[i + 1].longitude);
        sum += (+dis);
      }
      that.setData({
        sum: that.format(sum.toFixed(2))
      })
      console.log(sum);
    }, 3000)
    that.countTime();
    that.setData({
      switch: !this.data.switch
    })
  },
复制代码

We see the start function, there are two timers, one for continued access to the first and draw lines of latitude and longitude, and the second to continue to calculate the distance. Multi-function involved is really, getLocation, drawLine, getDistance, format, countTime, look at how they are implemented.

// 获取经纬度
  getLocation() {
    var latitude1, longitude1;
    wx.getLocation({
      type: 'gcj02',
      success: res => {
        latitude1 = res.latitude;
        longitude1 = res.longitude;
        this.setData({
          latitude: latitude1,
          longitude: longitude1
        })
        point.push({
          latitude: latitude1,
          longitude: longitude1
        });
        console.log(point);
      }
    })
  },
  // 画线
  drawLine() {
    this.setData({
      polyline: [{
        points: point,
        color: "#1298db",
        width: 4
      }]
    })
  },
  // 进行经纬度转换为距离的计算
  rad(d) {
    // 经纬度转换成三角函数中度分表形式
    return d * Math.PI / 180.0;
  },
  // 计算距离,参数分别为第一点的纬度,经度;第二点的纬度,经度
  getDistance(lat1, lng1, lat2, lng2) {
    let that = this;
    var radLat1 = that.rad(lat1);
    var radLat2 = that.rad(lat2);
    var a = radLat1 - radLat2;
    var b = that.rad(lng1) - that.rad(lng2);
    var s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
    // 地球半径
    s = s * 6378.137;
    // 输出为公里
    s = Math.round(s * 10000) / 10000;
    // s = s.toFixed(2);
    return s;
  },
  format(str) {
    str = '' + str;
    return str.length === 1 ? `0.0${str}` : str;
  },
  format1(str) {
    str = '' + str;
    return str.length === 1 ? `0${str}` : str;
  },
  countTime() {
    this.tim = setInterval(() => {
      cur++;
      time.setMinutes(cur / 60);
      time.setSeconds(cur % 60);
      this.setData({
        time: '00:' + this.format1(time.getMinutes()) + ':' + this.format1(time.getSeconds())
      })
    }, 1000)
  },
复制代码

rad and getDistance function is used to latitude and longitude converted into kilometers away, which of course have to find on the Internet, do not ask me why. countTime function is used to calculate the time by format and format1 function to sum (km) and time (time) is converted to the format you want.

end() {
    console.log("clear");
    clearInterval(this.timer);
    clearInterval(cal);
    clearInterval(this.tim);
    this.setData({
      switch: !this.data.switch
    })
  },
  stop() {
    let markers1 = [];
    let marker1 = {
      iconPath: "../../images/terminal.png",
      id: 1,
      width: 40,
      height: 40
    };
    clearInterval(this.timer);
    clearInterval(cal);
    clearInterval(this.tim);
    marker1.latitude = point[point.length - 1].latitude;
    marker1.longitude = point[point.length - 1].longitude;
    markers1.push(marker1);
    this.setData({
      markers: this.data.markers.concat(markers1)
    })
    app.globalData.sum = this.data.sum;
    // console.log(app.globalData.sum)
    point = [];
    cur = 0;
    // wx.navigateBack();
  },
复制代码

end function is used to suspend running, and stop function is added to the end of the run and end markers.

Finally, mention that you want to display other dom structure would have to use cover-view labels, and can only be nested inside cover-view, it does not take component, which is the most pit in the map above, so I handwriting a similar pull-down menu on the component, we see the effect of the project show in the know to write very rough not posted code.

Epilogue

Writing seems a bit much, I do not spade.

Paste a github address it: run it (like a Star gave it, as an affirmation of the study authors.)

Guess you like

Origin blog.csdn.net/weixin_34351321/article/details/91396241