小程序初体验—仿CoCo奶茶

871 阅读9分钟

前言

总觉得小程序是一种神奇的存在,不用下载安装就能快速使用,而且还不会占用手机内存(毕竟我的手机内存需要留给各种帅哥,嘻嘻)。作为一个前端学习者,在钱的诱惑下决定自己手撸一个小程序。打开自己手机的小程序,发现长的好看一点的也就coco奶茶了,啧啧,那叫一个小清新,于是就开始了我的小程序之旅。终于在我坚持不懈的努力下,写出了个大概。在此分享我的一些心得,希望能给正在学习小程序的小伙伴们一些帮助。

开发工具

  • 微信小程序开发工具
  • 微信小程序官方文档
  • Vscode前端开发工具
  • Easy Mock一个可视化,并且能快速生成模拟数据的持久化服务。

小程序主要页面及其功能

首页

首页最主要的就是swiper滑动,这是小程序最常见的功能,其次是底部的tabBar,需要在app.json中先定义好,包括页面跳转,点击一个tab时颜色提亮

<swiper indicator-dots="{{true}}" indicator-active-color="#ff7e00" autoplay="{{autoplay}}" interval="{{interval}}" duration="{{duration}}">
  <block wx:for="{{imgUrls}}"  wx:key="index">
    <swiper-item>
      <image src="{{item}}" class="slide-img" mode='aspectFill' />
    </swiper-item>
  </block>
</swiper>
Page({
  data: {
  imgUrls:[
    '../../images/1.jpg',
    '../../images/2.jpg',
    '../../images/3.jpg',
    '../../images/4.jpg'
  ],
  autoplay:true,
  interval:3000,
  duration:500
  },
  order: function(opotions){
    wx.navigateTo({
      url: '../../pages/choose/choose',
    })
  },
})

首页及点击tab截图,在我的页面中获取头像和昵称

{
  "pages": [
    "pages/index/index",
    "pages/choose/choose",
    "pages/destination/destination",
    "pages/address/address",
    "pages/order/order",
    "pages/mine/mine",
    "pages/position/position"
  ],
  "window": {
    "backgroundTextStyle": "light",
    "navigationBarBackgroundColor": "#fff",
    "navigationBarTitleText": "CoCo都可",
    "navigationBarTextStyle": "black"
  },
  "tabBar": {
    "color": "#b7b7b7",
    "selectedColor": "#AB956D",
    "borderStyle": "black",
    "backgroundColor": "#f5f5f5",
    "selectedindex": 1,
    "list": [
      {
        "pagePath": "pages/index/index",
        "iconPath": "/images/首页1.png",
        "selectedIconPath": "/images/首页.png",
        "text": "首页"
      },
      {
        "pagePath": "pages/order/order",
        "iconPath": "/images/订单1.png",
        "selectedIconPath": "/images/订单.png",
        "text": "订单"
      },
      {
        "pagePath": "pages/mine/mine",
        "iconPath": "/images/我的1.png",
        "selectedIconPath": "/images/我的.png",
        "text": "我的"
      }
    ]
  },
  "sitemapLocation": "sitemap.json"
}

点单页面

点单页面的奶茶数据是自己从CoCo官网取过来的,然后把他们放在easymock里建一个接口,建接口的时候没考虑那么仔细以至于后面获取数据里面的单个数据遇到坑,不想修改数据,最后只能使用冒泡法来判断每一种奶茶属于哪一个系列。然后就是加减购物车,自己手撸了一个组件,点击加号时弹出组件,选择奶茶属性,点击减号数量和总价相应的减少,总价往回减的时候遇到一个坑,就是把js中加号变为减号时,总价变成负号,奈何js功底不强,只能投机取巧取一个绝对值。也算是实现了效果。这里还有就是要知道父组件与子组件间的通信,把父组件的数据传入子组件。

父组件

<view class="main">
    <view class="category-left">
        <view wx:for="{{category}}" wx:key="{{item.id}}" data-id="{{item.id}}" data-index="{{index}}" class="cate-list {{curIndex === index ? 'on' : ''}}" bindtap="switchTap">
            {{item.name1}}
        </view>
    </view>
    <scroll-view class="category-right" scroll-y scroll-into-view="{{toView}}" scroll-with-animation="{{true}}" style="height:{{conHeight}}rpx;" bindscroll="onScroll">
        <block wx:for="{{detail}}" wx:key="{{item.id}}">
            <view id="{{item.id}}" bindtap="test" data-index="{{index}}">
                <view class="cate-title">
                    <text>{{item.cate}}</text>
                </view>
                <view class="cate-box" wx:for="{{item.detail}}" wx:key="{{item.id}}">
                    <image src="{{item.images1}}" class="img1" />
                    <view class="drink-name">{{item.name}}</view>
                    <view class="drink-box">
                        <text class="drink-price">¥{{item.price}}</text>
                        <image src="{{item.images3}}" class="drink-reduce" bindtap="reduceCount" data-index="{{index}}"/>
                        <text class="num">{{item.num}}</text>
                        <image src="{{item.images2}}" class="drink-add" bindtap="addCount" data-index="{{index}}" data-name="{{item.name}}" data-price="{{item.price}}" />
                    </view>
                </view>
            </view>
        </block>
    </scroll-view>
    <count id="count" title="{{countname}}" money="{{countprice}}" nums="{{countnum}}">
    </count>
    <!-- <information id="information"></information> -->
    <view class="footer">
        <view class="total"> 
            <image src="../../images/购物车1.png" class="quantity" bindtap="Selected" />
            <view wx:if="{{isShow}}">
                <view class="total_quantity">{{totaledquantity}}</view>
            </view>
        </view>
        <view class="sum">¥{{totalprice}}</view>
        <view class="account" bindtap="Account">去结算</view>
        <image src="../../images/arrows.png" class="arrows" />
    </view>
</view>
const API = 'https://www.easy-mock.com/mock/5ce7ede7b3ab8d779e20e856/Drink/Drink'
Page({
  data:{
    totalprice:0,
    totaledquantity:0,
    isShow:0,
    category:[
      {name1:'季节限定',id:'season'},
      {name1:'醇香奶茶',id:'chunxiang'},
      {name1:'醇黑浓情',id:'chunhei'},
      {name1:'鲜果鲜茶',id:'xianguo'},  
      {name1:'白色恋人',id:'baise'},
      {name1:'咖啡时光',id:'cofei'},
    ],
    detail:[],
    countname:0,
    countprice:0,
    countnum:0,
    menuIndex: 0
  },
  test (e) {
    console.log(e.currentTarget.dataset.index)
    this.setData({
      menuIndex: e.currentTarget.dataset.index
    })
  },
  addCount:function(e){
    console.log(e.currentTarget.dataset.index)
    this.count.show();
    this.setData({
      countname:e.target.dataset.name,
      countprice:e.target.dataset.price,
    })
    const index = e.target.dataset.index
    console.log(e);
    let carts = this.data.detail
    console.log(carts)
    let num = carts[this.data.menuIndex].detail[index].num 
    console.log(num)
    let price = carts[this.data.menuIndex].detail[index].price
    console.log(price)
    num = num + 1
    carts[this.data.menuIndex].detail[index].num = num
    console.log(carts)
    this.setData({
      detail:carts,
      isShow:1,
      countnum:num
    })
    this.getTotalprice()
    this.getTotalnum()
  },
  reduceCount: function(e){
    const index = e.target.dataset.index 
    console.log(e);
    let carts = this.data.detail
    console.log(carts)
    let num = carts[this.data.menuIndex].detail[index].num
    console.log(num)
    if(num > 0){
      num = num - 1
    }else{
      return num 
    }
    carts[this.data.menuIndex].detail[index].num = num
    console.log(carts)
    this.setData({
      detail:carts
    })
    this.getreducenum()  
    this.getreduceprice() 
  },
  getTotalprice(){
    let carts = this.data.detail
    let total = 0
    let total1 = 0
    for(let i = 0; i<carts.length;i++){
      for(let j = 0; j<carts[i].detail.length;j++){
        if(carts[i].detail[j].price){
        total +=carts[i].detail[j].num * carts[i].detail[j].price
        total1 =(parseFloat(total)).toFixed(1)
        }
      }
    }
    this.setData({
      totalprice: total1
    })
  },
  getreduceprice(){
    let carts = this.data.detail
    let total2 = 0
    for(let i = 0; i<carts.length;i++){
      for(let j = 0; j<carts[i].detail.length;j++){
        if(carts[i].detail[j].price){
        total2 -=carts[i].detail[j].num * carts[i].detail[j].price
        var total3 = (parseFloat(Math.abs(total2))).toFixed(1) 
        }
      }
    }
    this.setData({
      totalprice: total3
    })
  },
  getTotalnum(){
    let carts = this.data.detail
    let totalnum = 0
    for(let i = 0; i<carts.length;i++){
      for(let j = 0; j<carts[i].detail.length;j++){
        if(carts[i].detail[j].num){
          totalnum +=carts[i].detail[j].num 
        }
      }
    }
    this.setData({
      totaledquantity: totalnum
    })
  },
  getreducenum(){
    let carts = this.data.detail
    let totalnum = 0
    for(let i = 0; i<carts.length;i++){
      for(let j = 0; j<carts[i].detail.length;j++){
        if(carts[i].detail[j].num){
          totalnum =carts[i].detail[j].num 
        }
      }
    }
    this.setData({
      totaledquantity: totalnum,
    })
  },
  switchTap(e){
    console.log(e);
    this.setData({
      toView:e.target.dataset.id,
      curIndex:e.target.dataset.index
    })
  },
  onLoad: function (options) {
    let self = this
    wx.request({
      url:API,
      method:'get',
      success(res){
        self.setData({
        detail:res.data
        })
        console.log(res);
      }
    })
  },

  onReady: function () {
    this.count = this.selectComponent('#count');
    // this.information = this.selectComponent('#information');
  },
   Account: function(){
    var that = this
    wx.navigateTo({
      url: '../../pages/destination/destination?allprice=' + that.data.totalprice +
       '&allquantity=' + that.data.totaledquantity + '&show=' + that.data.isShow
    })
  },

子组件

在子组件中选择属性时会使其背景颜色发生改变,原理是在点击属性框时给其添加一个类名active

<!-- components/count/count.wxml -->
<view class="wx-count" hidden="{{flag}}" bindtap="Close">
    <view class="count-container" >
        <view class="count-title">{{title}}</view>
        <view class="count-metarial">{{metarial}}</view>
        <view class="main1">
            <button wx:for="{{metarials}}" wx:key="index" class="count-normal {{item.active ? 'active' : ''}}" bindtap="Change" data-taget="{{item.name}}" data-index="{{index}}">{{item.name}}</button>
        </view>
        <view class="count-temperature">{{temperature}}</view>
        <view class="main2">
            <button wx:for="{{temperatures}}" wx:key="index" class="normal-ice {{item.active1 ? 'active1' : ''}}" bindtap="Change1" data-taget="{{item.name}}" data-index="{{index}}">{{item.name}}</button>
        </view>
        <view class="count-sugar">{{sugar}}</view>
        <view class="main3">
            <button wx:for="{{sugars}}" wx:key="index" class="normal-sugar {{item.active2 ? 'active2' : ''}}" bindtap="Change2" data-taget="{{item.name}}" data-index="{{index}}">{{item.name}}</button>
        </view>
        <view class="count-prcie">{{money}}</view>
        <!-- <view class="count-num">{{nums}}</view> -->
        <button class="count-add" bindtap="Add">{{add}}</button>
    </view>
</view>
// components/count/count.js
const app = getApp()
Component({
  /**
   * 组件的属性列表
   */
  options: {
    multipleSlots: true // 在组件定义时的选项中启用多slot支持
  },

  // parent
  properties: {
    title:{
      type: String,
      value: ''
    },
    money:{
      type: String,
      value:''
    },
   nums:{
     type: Number,
     value:''
   }
  },

  /**
   * 组件的初始数据
   */
  data: {
    flag:true,
    // title:'',
    metarial:'加料',
    metarials: [
      {name:'常规'},
      {name:'珍珠'},
      {name:'椰果'}
    ],
    temperature:'温度',
    temperatures:[
      {name:'常温'},
      {name:'多冰'},
      {name:'少冰'},
      {name:'去冰'},
    ], 
    sugar:'糖度',
    sugars:[
      // {name:'常规糖'},
      {name:'半糖'},
      {name:'微糖'},
      {name:'不加糖'},
    ],
    // money:'',
    add:'加入购物车',
    flag: true,
    isActive: false,
    cart: []
  },

  /**
   * 组件的方法列表
   */
  methods: {
    Change:function(e){
      var that = this
      let index = e.currentTarget.dataset.index
      console.log(e.currentTarget.dataset.index);
      for (let i = 0; i < that.data.metarials.length; i++) {
        let cencal = `metarials[${i}].active`
        that.setData({
          [cencal]: false
        })
      }
      let test = `metarials[${index}].active`
      that.setData({
        [test]: true
      })
      app.globalData.cart.metarial = that.data.metarials[index].name || '常规'
      console.log(app.globalData.cart);
    },
    Change1: function(e){
      var that = this
      let index = e.currentTarget.dataset.index
      console.log(e.currentTarget.dataset.index);
      for (let j = 0; j < that.data.temperatures.length; j++) {
        let cencal1 = `temperatures[${j}].active1`
        that.setData({
          [cencal1]: false
        })
      }
      let test1 = `temperatures[${index}].active1`
      that.setData({
        [test1]: true
      })
      app.globalData.cart.temperature = that.data.temperatures[index].name || '常温'
      console.log(app.globalData.cart);
    },
    Change2: function(e){
      var that = this
      let index = e.currentTarget.dataset.index
      console.log(e.currentTarget.dataset.index);
      for (let k = 0; k < that.data.sugars.length; k++) {
        let cencal2 = `sugars[${k}].active2`
        that.setData({
          [cencal2]: false
        })
      }
      let test2 = `sugars[${index}].active2`
      that.setData({
        [test2]: true
      })
      app.globalData.cart.sugar = that.data.sugars[index].name || '半糖'
      console.log(app.globalData.cart);
    },
      //隐藏弹框
      hide: function () {
        this.setData({
          flag: true
        })
      },
      //展示弹框
      show: function () {
        this.setData({
          flag: false
        })
     },
     Add: function(e){
       var that = this;
       console.log(this.data.metarials);
       if (!app.globalData.cart.metarial) {
         app.globalData.cart.metarial = '常规'
       }
       if (!app.globalData.cart.C) {
         app.globalData.cart.temperature = '常温'
       }
       if (!app.globalData.cart.sugar) {
         app.globalData.cart.sugar = '半糖'
       }
       app.globalData.cart.title = that.data.title
       app.globalData.cart.money = that.data.money
       app.globalData.cart.nums = that.data.nums
       app.globalData.carts.push(app.globalData.cart)
       app.globalData.cart = {}
       console.log(app.globalData.carts);
       this.setData({
         flag:true
       })
     },
}
})

订单页面

订单页面包括取单地址的选择,点单页面中选择的数据渲染到这个页面中来。地址选择用到小程序中的 wx.chooseLocation方法,首先在全局中引入qqmap-wx-jssdk.js文件,然后需要有一个腾讯地图的密钥。在全局中定义经纬度,地址名,详细地址。然后去到订单页面获取地图取得的数据。订单列表用一个swiper装起来,同样的把点单页面获取的订单数据放到全局中,再去订单页面中获取后渲染到页面上。底部的购物车总数量和总价格由上一个页面跳转时,利用url带参传过来。这里有一个坑就是,传数据的时候数据类型会发生改变,需要到接收页面的js中使其转换数据类型。也就是我设置的isShow初始化的数据类型传过来会是string,因此需要变回Numbe类型,不然购物车在没有数量的情况下隐藏的0就会显示出来。

const QQMapWX = require('./libs/qqmap-wx-jssdk.js');
const  qqmapsdk = new QQMapWX({
      key: '56JBZ-HXH6P-OR7DG-VF2DH-NZVPS-OFFRO'
      });
//app.js
App({
  onLaunch: function () {
    // 展示本地存储能力
    var logs = wx.getStorageSync('logs') || []
    logs.unshift(Date.now())
    wx.setStorageSync('logs', logs)

    // 登录
    wx.login({
      success: res => {
        // 发送 res.code 到后台换取 openId, sessionKey, unionId
      }
    })
    // 获取用户信息
    wx.getSetting({
      success: res => {
        if (res.authSetting['scope.userInfo']) {
          // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
          wx.getUserInfo({
            success: res => {
              // 可以将 res 发送给后台解码出 unionId
              this.globalData.userInfo = res.userInfo

              // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
              // 所以此处加入 callback 以防止这种情况
              if (this.userInfoReadyCallback) {
                this.userInfoReadyCallback(res)
              }
            }
          })
        }
      }
    })
  },
  globalData: {
    userInfo: null,
    destination:'',
    qqmapsdk,
    activity_lat: null,
    activity_lng:null,
    activity_location:null,
    activity_address:null,
    cart: {},
    carts: []
  }
})
const app = getApp()

Page({
  data: {
    latitude: 0,//地图初次加载时的纬度坐标
    longitude: 0, //地图初次加载时的经度坐标
    name: "" ,//选择的位置名称
    address:"",
    totaledquantity:0,
    totalprice:0,
    isShow: 0,
    Hidden: true,
    selectAllStatus: false
  },
  Account: function(){
    this.setData({
      Hidden:false
    })
  },
  Close: function(){
    this.setData({
      Hidden:true
    })
  },
  Select: function() {
    let selectAllStatus = this.data.selectAllStatus
    selectAllStatus = !selectAllStatus
    this.setData({
      selectAllStatus: selectAllStatus
    })
  },
  Gopay: function(){
    wx.switchTab({
      url: '../../pages/index/index',
    })
  },
  Choose: function(res){
    let self = this
    wx.chooseLocation({
      success: function(res){
        console.log(res);
        app.globalData.activity_lat = res.latitude;
        app.globalData.activity_lng = res.longitude;
        app.globalData.activity_location = res.name;
        app.globalData.activity_address = res.address;
        self.setData({
          name: res.name,
          address:res.address
        })
        console.log(app.globalData.activity_location);
      },
    })  
  },
  onLoad: function (options) {
    console.log(typeof(options.show))
    var that = this
    that.setData({
      totaledquantity: options.allquantity,
      isShow: Number(options.show) || 0,
      totalprice: options.allprice
    })   
  },
  onReady: function () {
    let carts = app.globalData.carts
    this.setData({
      carts:carts
    })
    console.log(app.globalData.carts)
  },

})

结算页面

这里跳出的弹窗用了一个action-sheet组件,支付方式勾选用到两个icon,在icon进行if和else判断。 点击确认支付按钮跳到首页,如果前面已经用到wx.navigateTo跳转过该页面,那就可以换wx.switchTab跳转哦。

 <action-sheet hidden="{{Hidden}}" >
     <view class="mainBox">
       <view class="bigBox">
         <view class="top_box">
           <view class="close" bindtap="Close">X</view>
           <view class="pay_title">支付</view>
           <view class="sum_price">订单总价</view>
           <view class="totaledPrice">¥{{totalprice}}</view>
         </view>
         <view class="mindle_box">
           <view class="pay_way">支付方式</view>
           <view class="pay_choose">
             <view class="icon">
             <image src="../../images/微信支付.png" class="weichat_pay"/>
           </view>
           <view class="wechat">微信支付</view>
           <icon wx:if="{{selectAllStatus}}" bindtap="Select" class="pay_select" color="#f17c0e" type="success_circle" />
           <icon wx:else type="circle" color="#f17c0e" bindtap="Select" class="pay_select"/>
           </view>
         </view>
         <view class="under_box">
           <view class="confirm" bindtap="Gopay">
             <view class="words">确认支付{{totalprice}}元</view>
           </view>
         </view>
       </view>
     </view>
    </action-sheet>

全部的效果图展示

寄语

以上是我对我小程序简单粗暴式的总结,作为一个初学小程序的小菜鸟,还有许多不足和需要学习的地方。望各位走过路过的小可爱们给我点个赞哈。 附上我的源码地址[github.com/Yanhuan111/…]