微信小程序 電商項目

Token

接口增加訪問權限,當用戶登錄後要確定用戶的身份,這就是token令牌,當然他要有有效期

微信身份體系設計

小程序有一套自己的令牌機制,所以不用我們後端再寫一套,小程序登陸開發API獲取code後,調取後臺獲取令牌的token接口,再到微信服務器,返回openid,session_key,openid是用戶的唯一標識,切記它不能返回給客戶端,支付的時候會用到。受保護的接口一定要攜帶token

瞭解uid,根據uid查詢用戶的數據

如果關聯公衆號,服務號,小程序,同一個用戶要保證ID的不變,這就要用到uid

下面正式開始小程序

開發前的配置、開發經驗技巧

  • 合法域名的配置,在公衆號設置裏面
  • 開發工具中的關於https的設置
  • 在項目中新建文件回車後自動生成四類文件
  • app.json裏面配置路由
  • 數據循環時作用於block標籤 block 並不是一個組件,它僅僅是一個包裝元素,不會在頁面中做任何渲染,只接受控制屬性,所以不能寫樣式。
  • model文件,存放數據接口,js文件負責數據雙向綁定。
  • 佈局的時候如果,container時列,再包裹一層view設置橫向佈局

關於token重發機制很重要,app.js在小程序初始化後校驗令牌,小程序沒有登錄賬號密碼獲取code碼對用戶來說完全是透明的,

官方小程序 app.js

globalData用於存放用戶的個人信息,關注小程序生命週期的理解

//app.js
App({
  onLaunch: function () {
    // 展示本地存儲能力
    var logs = wx.getStorageSync('logs') || []
    logs.unshift(Date.now())
    wx.setStorageSync('logs', logs)
  },
  globalData: {
    userInfo: null
  }
})

項目中的 app.js

//app.js
import { Token } from 'utils/token.js';

App({
  onLaunch: function () {
      var token = new Token();
      token.verify();
  },

  onShow:function(){
  
  },
})

app.json

{
  "pages":[
    "pages/home/home",
    "pages/category/category",
    "pages/cart/cart",
    "pages/my/my",
    "pages/theme/theme",
    "pages/product/product",
    "pages/order/order",
    "pages/pay-result/pay-result"
  ],
  "window":{
    "navigationBarTitleText": "",
    "navigationBarTextStyle": "#FFFFFF",
    "navigationBarBackgroundColor": "#AB956D",
    "backgroundColor": "#FFFFFF",
    "backgroundTextStyle": "dark",
    "enablePullDownRefresh":true
  },
  "tabBar":{
    "list":[{
        "pagePath":"pages/home/home",
        "iconPath":"imgs/toolbar/home.png",
        "selectedIconPath":"imgs/toolbar/[email protected]",
        "text":"主頁"
      },{
        "pagePath":"pages/category/category",
        "iconPath":"imgs/toolbar/category.png",
        "selectedIconPath":"imgs/toolbar/[email protected]",
        "text":"分類"
      },{
        "pagePath":"pages/cart/cart",
        "iconPath":"imgs/toolbar/cart.png",
        "selectedIconPath":"imgs/toolbar/[email protected]",
        "text":"購物車"
      },{
        "pagePath":"pages/my/my",
        "iconPath":"imgs/toolbar/my.png",
        "selectedIconPath":"imgs/toolbar/[email protected]",
        "text":"我的"
      }],
      "backgroundColor": "#F5F5F5",
      "selectedColor": "#AB956D",
      "color": "#989898",
      "borderStyle": "white",
      "position":"bottom"
  },
  "networkTimeout": {
    "request": 20000,
    "connectSocket": 20000,
    "uploadFile": 20000,
    "downloadFile": 20000
  },
  "debug":true
}

編程技巧 源自官方小程序

關注它末尾補零的技巧、還有返回字符串的拼接

util.js

const formatTime = date => {
  const year = date.getFullYear()
  const month = date.getMonth() + 1
  const day = date.getDate()
  const hour = date.getHours()
  const minute = date.getMinutes()
  const second = date.getSeconds()

  return [year, month, day].map(formatNumber).join('-') + ' ' + [hour, minute, second].map(formatNumber).join(':')
}

const formatNumber = n => {
  n = n.toString()
  return n[1] ? n : '0' + n
}

module.exports = {
  formatTime: formatTime
}

//logs.js
const util = require('../../utils/util.js')

Page({
  data: {
    logs: []
  },
  onLoad: function () {
    this.setData({
      logs: (wx.getStorageSync('logs') || []).map(log => {
        return util.formatTime(new Date(log))
      })
    })
  }
})

// 引用使用es6的module引入和定義
// 全局變量以g_開頭
// 私有函數以_開頭

utils/token.js


import { Config } from 'config.js';

class Token {
    constructor() {
        this.verifyUrl = Config.restUrl + 'token/verify';
        this.tokenUrl = Config.restUrl + 'token/user';
    }

    verify() {
        var token = wx.getStorageSync('token');
        if (!token) {
            this.getTokenFromServer();
        }
        else {
            this._veirfyFromServer(token);
        } 
    }

    _veirfyFromServer(token) {
        var that = this;
        wx.request({
            url: that.verifyUrl,
            method: 'POST',
            data: {
                token: token
            },
            success: function (res) {
                var valid = res.data.isValid;
                if(!valid){
                    that.getTokenFromServer();
                }
            }
        })
    }

    getTokenFromServer(callBack) {
        var that  = this;
        wx.login({
            success: function (res) {
                wx.request({
                    url: that.tokenUrl,
                    method:'POST',
                    data:{
                        code:res.code
                    },
                    success:function(res){
                        wx.setStorageSync('token', res.data.token);
                        callBack&&callBack(res.data.token);
                    }
                })
            }
        })
    }
}

export {Token};

utils/config


class Config{
    constructor(){

    }
}

Config.restUrl = 'REST API 基地址';
Config.onPay=true;  //是否啓用支付

export {Config};

utils/base
回調函數的應用,令牌重獲機制(按項目需求是否需要)

注意回調函數的處理,調用方法的時候傳入就返回,沒有則不執行

import { Token } from 'token.js';
import { Config } from 'config.js';

class Base {
    constructor() {
        "use strict";
        this.baseRestUrl = Config.restUrl;
        this.onPay=Config.onPay;
    }

    //http 請求類, 當noRefech爲true時,不做未授權重試機制
    request(params, noRefetch) {
        var that = this,
            url=this.baseRestUrl + params.url;
        if(!params.type){
            params.type='get';
        }
        /*不需要再次組裝地址*/
        if(params.setUpUrl==false){
            url = params.url;
        }
        wx.request({
            url: url,
            data: params.data,
            method:params.type,
            header: {
                'content-type': 'application/json',
                'token': wx.getStorageSync('token')
            },
            success: function (res) {

                // 判斷以2(2xx)開頭的狀態碼爲正確
                // 異常不要返回到回調中,就在request中處理,記錄日誌並showToast一個統一的錯誤即可
                var code = res.statusCode.toString();
                var startChar = code.charAt(0);
                if (startChar == '2') {
                    params.sCallback && params.sCallback(res.data);
                } else {
                    if (code == '401') {
                        if (!noRefetch) {
                            that._refetch(params);
                        }
                    }
                    that._processError(res);
                    params.eCallback && params.eCallback(res.data);
                }
            },
            fail: function (err) {
                //wx.hideNavigationBarLoading();
                that._processError(err);
                // params.eCallback && params.eCallback(err);
            }
        });
    }

    _processError(err){
        console.log(err);
    }

    _refetch(param) {
        var token = new Token();
        token.getTokenFromServer((token) => {
            this.request(param, true);
        });
    }

    /*獲得元素上的綁定的值*/
    getDataSet(event, key) {
        return event.currentTarget.dataset[key];
    };

};

export {Base};

home/home.wxml

模板的引入,以及往模版內傳數據,詳解了數據如何去渲染,以及block的使用,for循環,巧用if判斷 、樣式的引入 @import "../tpls/base.wxss"; @import "../tpls/products/products-tpl.wxss";

下面是模版

<template name="products">
    <view class="products-box">
        <block wx:for="{{productsArr}}">
            <view class="products-item" bindtap="onProductsItemTap" data-id="{{item.id}}">
                <image class="products-image" src="{{item.main_img_url}}" mode="aspectFill"></image>
                <view class="products-item-bottom">
                    <text class="name">{{item.name}}</text>
                    <view class="price">{{item.price}}</view>
                </view>
            </view>
        </block>
    </view>
</template>

首頁如何使用模版

<import src="../tpls/products/products-tpl.wxml"/>
<view class="container home-container" hidden="{{!loadingHidden}}">
    <swiper indicator-dots="true" autoplay="true" class="swiper">
        <block wx:for="{{bannerArr}}">
            <swiper-item class="banner-item" bindtap="onProductsItemTap" data-id="{{item.key_word}}">
                <image class="item-image" src="{{item.img.url}}" mode="aspectFill" />
            </swiper-item>
        </block>
    </swiper>
    <view class="home-main">
        <!--主題精選-->
        <view class="home-main-theme">
            <view class="home-main-header">精選主題</view>
            <view class="theme-box">
                <block wx:for="{{themeArr}}">
                    <view wx:if="{{index==2}}" class="theme-item big" bindtap="onThemesItemTap" data-id="{{item.id}}" data-name="{{item.name}}">
                        <image src="{{item.topic_img.url}}"></image>
                    </view>
                    <view wx:else class="theme-item" bindtap="onThemesItemTap" data-id="{{item.id}}" data-name="{{item.name}}">
                        <image src="{{item.topic_img.url}}"></image>
                    </view>
                </block>
            </view>
        </view>
        <!--單品首發-->
        <view class="home-main-products">
            <view class="home-main-header">最近新品</view>
            <template is="products" data="{{productsArr:productsArr}}"/>
        </view>
    </view>
</view>
<loading hidden="{{loadingHidden}}">
    加載中...
</loading>

home/home.model.js

// var Base = require('../../utils/base.js').base;
import {Base} from '../../utils/base.js';

class Home extends Base{
    constructor(){
        super();
    }

    /*banner圖片信息*/
    getBannerData(callback){
        var that=this;
        var param={
            url: 'banner/1',

            sCallback:function(data){
                data=data.items;
                callback && callback(data);
            }
        };
        this.request(param);
    }
    /*首頁主題*/
    getThemeData(callback){
        var param={
            url: 'theme?ids=1,2,3',
            sCallback:function(data){
                callback && callback(data);
            }
        };
        this.request(param);
    }

    /*首頁部分商品*/
    getProductorData(callback){
        var param={
            url: 'product/recent',
            sCallback:function(data){
                callback && callback(data);
            }
        };
        this.request(param);
    }
};

export {Home};

home/home.js

import { Home } from 'home-model.js';
var home = new Home(); //實例化 首頁 對象
Page({
    data: {
        loadingHidden: false
    },
    onLoad: function () {
        this._loadData();
    },

    /*加載所有數據*/
    _loadData:function(callback){
        var that = this;

        // 獲得bannar信息
        home.getBannerData((data) => {
            that.setData({
                bannerArr: data,
            });
        });

        /*獲取主題信息*/
        home.getThemeData((data) => {
            that.setData({
                themeArr: data,
                loadingHidden: true
            });
        });

        /*獲取單品信息*/
        home.getProductorData((data) => {
            that.setData({
                productsArr: data
            });
        });
    },

    /*跳轉到商品詳情*/
    onProductsItemTap: function (event) {
        var id = home.getDataSet(event, 'id');
        wx.navigateTo({
            url: '../product/product?id=' + id
        })
    },

    /*跳轉到主題列表*/
    onThemesItemTap: function (event) {
        var id = home.getDataSet(event, 'id');
        var name = home.getDataSet(event, 'name');
        wx.navigateTo({
            url: '../theme/theme?id=' + id+'&name='+ name
        })
    },

    /*下拉刷新頁面*/
    onPullDownRefresh: function(){
        this._loadData(()=>{
            wx.stopPullDownRefresh()
        });
    },

    //分享效果
    onShareAppMessage: function () {
        return {
            title: '零食商販 Pretty Vendor',
            path: 'pages/home/home'
        }
    }

})

注意setNavigationBarTitle動態設置導航標題,在生命週期裏面的應用

theme/theme.js

import { Theme } from 'theme-model.js';
var theme = new Theme(); //實例化  主題列表對象
Page({
    data: {
        loadingHidden: false
    },
    onReady:function(){
        wx.setNavigationBarTitle({
            title: this.data.titleName
        });
    },
    onLoad: function (option) {
        this.data.titleName=option.name;
        this.data.id=option.id;
        wx.setNavigationBarTitle({
            title: option.name
        });
        this._loadData();

    },

    /*加載所有數據*/
    _loadData:function(callback){
        var that = this;
        /*獲取單品列表信息*/
        theme.getProductorData(this.data.id,(data) => {
            that.setData({
                themeInfo: data,
                loadingHidden:true
            });
        });
    },

    /*跳轉到商品詳情*/
    onProductsItemTap: function (event) {
        var id = theme.getDataSet(event, 'id');
        wx.navigateTo({
            url: '../product/product?id=' + id
        })
    },

    /*下拉刷新頁面*/
    onPullDownRefresh: function(){
        this._loadData(()=>{
            wx.stopPullDownRefresh()
        });
    },

    //分享效果
    onShareAppMessage: function () {
        return {
            title: '零食商販 Pretty Vendor',
            path: 'pages/theme/theme?id=' + this.data.id
        }
    }

})

產品詳情頁面,picker組件的應用如何從picker裏面取值,tab切換的思路,產品詳情下面三個tab盒子切換的條件,tab切換關鍵點在於currentindex,最近在做react native 遇到父組件跳到子組件激活子組件樣式,子組件接收到就修改currentindex,否則還是默認爲0,默認定義以後就不要動了,直接把傳入的值賦值給它

在這裏插入圖片描述

product.wxml

<view class="container detail-container" hidden="{{!loadingHidden}}">
    <view class="detail-header-box">
        <view class="fixed-btns-box" bindtap="onCartTap">
            <view class="fiexd-cart {{isShake?'animate':''}}">
                <image src="../../imgs/icon/[email protected]"></image>
                <view wx:if="{{cartTotalCounts>0}}">{{cartTotalCounts}}</view>
            </view>
        </view>
        <view class="detail-topic-img">
            <image src="{{product.main_img_url}}" mode="aspectFit"></image>
        </view>
        <view class="cart-box">
            <view class="product-counts">
                <picker class="{{product.stock==0?'disabled':''}}" bindchange="bindPickerChange" value="{{index}}" range="{{countsArray}}">
                    <!--因爲picker對flex支持不好,所以加了一層view-->
                    <view>
                        <text class="counts-tips">數量</text>
                        <text class="counts-data">{{productCounts}}</text>
                        <image class="counts-icon" src="../../imgs/icon/[email protected]"></image>
                    </view>
                </picker>
            </view>
            <view class="middle-border"></view>
            <view class="add-cart-btn {{product.stock==0?'disabled':''}}" bindtap="onAddingToCartTap">
                <text>加入購物車</text>
                <image class="cart-icon" src="../../imgs/icon/cart.png"></image>
                <image id="small-top-img" class="small-top-img {{isFly?'animate':''}}"
                       src="{{product.main_img_url}}" mode="aspectFill" style="{{translateStyle}}"></image>
            </view>
        </view>
        <view class="basic-info-box">
            <view class="stock" wx:if="{{product.stock>0}}">有貨</view>
            <view class="stock no" wx:else>缺貨</view>
            <view class="name">{{product.name}}</view>
            <view class="price">{{product.price}}</view>
        </view>
    </view>
    <view class="detail-bottom-box">
        <view class="tabs-box">
            <block wx:for="{{['商品詳情' ,'產品參數','售後保障']}}">
                <view class="tabs-item {{currentTabsIndex==index?'selected':''}}" bindtap="onTabsItemTap" data-index="{{index}}">
                    {{item}}
                </view>
            </block>
        </view>
        <view class="product-detail-box">
            <view class="product-detail-imgs" hidden="{{currentTabsIndex!=0}}">
                <block wx:for="{{product.imgs}}">
                    <image src="{{item.img_url.url}}" mode="aspectFill"></image>
                </block>
            </view>
            <view class="product-detail-properties" hidden="{{currentTabsIndex!=1}}">
                <block wx:for="{{product.properties}}">
                    <view class="properties-item">
                        <view class="properties-name">{{item.name}}</view>
                        <view class="properties-detail">{{item.detail}}</view>
                    </view>
                </block>
            </view>
            <view class="product-detail-protect" hidden="{{currentTabsIndex!=2}}">
                <view>七天無理由免費退貨</view>
            </view>
        </view>
    </view>
</view>
<loading hidden="{{loadingHidden}}">
    加載中...
</loading>

主要是加入購物車的動畫

product.wxss

@import "../tpls/base.wxss";
.detail-container {
  background-color:#F9F9F9
}
.detail-header-box,.detail-bottom-box{
  background-color: #fff;
}
.detail-topic-img{
  display: flex;
  justify-content: center;
}
.detail-topic-img image{
  width: 100%;
}


.fixed-btns-box{
  position: fixed;
  top:50rpx;
  right:12px;
  width: 80rpx;
}
.fiexd-cart image{
  height: 64rpx;
  width: 64rpx;
}
.fiexd-cart view{
  font-size: 24rpx;
  background-color: #AB956D;
  color: white;
  position: absolute;
  right: 64rpx;
  top: 0rpx;
  height: 36rpx;
  width: 36rpx;
  line-height: 36rpx;
  border-radius: 36rpx;
  text-align: center;
}
.fiexd-cart.animate{
  animation: aCartScale 200ms cubic-bezier(.17,.67,.83,.67);
  animation-fill-mode: backwards;
}

@-webkit-keyframes aCartScale{
  0%{
    -webkit-transform: scale(1.1);
  }
  100% {
    -webkit-transform: scale(1);
  }
}

/*選擇數量和添加到購物車*/
.cart-box{
  width: 660rpx;
  height: 100rpx;
  margin: 30rpx auto;
  border-radius: 100rpx;
  background-color: #AB956D;
  color: #fff;
  display: flex;
  align-items: center;
}

.product-counts,.add-cart-btn{
  height: 100%;
  display: flex;
  font-size: 24rpx;
  align-items: center;
  justify-content: center;
}
.product-counts{
  width: 50%;
}
.add-cart-btn{
  position: relative;
  flex: 1;
}
.add-cart-btn:active{
  color: #fff;
}
.add-cart-btn.disabled{
  color: #D5D5DB;
}

/*中間分割線*/
.middle-border{
  width: 2rpx;
  height: 30rpx;
  border-right: 1rpx #fff dotted;
}

.small-top-img{
  height: 160rpx;
  width: 160rpx;
  right:6rpx;
  position: absolute;
  opacity: 0;
}
.small-top-img.animate{
  opacity: 1;
  /*-webkit-transition:all 1000ms cubic-bezier(.4,.46,.3,1.31);*/
  -webkit-transition:all 1000ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
}


/*數量選擇器*/
.product-counts picker{
  margin: 0 auto;
  height: 100rpx;
  width: 100%;
  color: #fff;
}
.product-counts picker.disabled{
  color: #D5D5DB;
}
.product-counts picker view{
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100rpx;
}
.counts-tips,.counts-data,.counts-icon{
  margin: 0 20rpx;
}
.counts-data{
  font-size: 28rpx;
}
.counts-icon{
  height: 48rpx;
  width: 48rpx;
}

.add-cart-btn .cart-icon{
  margin-left: 40rpx;
  height: 32rpx;
  width: 32rpx;
}

/*價格和名稱*/
.basic-info-box{
  padding: 15rpx 0;
  color: #454552;
  text-align: center;
}
.basic-info-box>view{
  margin: 20rpx auto;
}
.basic-info-box .stock{
  font-size: 24rpx;
}
.basic-info-box .stock.no{
  color:#B42F2D;
}
.basic-info-box .name{
  font-size: 40rpx;
}
.basic-info-box .price{
  font-size: 38rpx;
}

/*商品詳情*/
.detail-bottom-box{
  margin-top: 30rpx;
}
.tabs-box{
  height: 90rpx;
  display: flex;
  justify-content: space-between;
  margin-bottom: 15rpx;
}
.tabs-item{
  width: 33.3%;
  color: #C7C7CB;
  font-size: 28rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  display: flex;
  border-bottom: 1rpx solid #D0D0D7;
}
.tabs-item.selected{
  /*color: #AB956D;*/
  color: rgba(171,149,109,.8);
  /*border-bottom: 2px solid #AB956D;*/
  border-bottom: 2px solid rgba(171,149,109,.8);
}
.product-detail-box{
  padding-bottom: 20rpx;
}
.product-detail-imgs image{
  width: 100%;
  height:400rpx;
  float: left;
}

.product-detail-properties,.product-detail-protect{
  min-height: 80vh;
}

.properties-item{
  display: flex;
  margin: 25rpx 0;
  font-size:24rpx;
}
.properties-name{
  width: 160rpx;
  color:#808080;
  text-align: center;
}
.properties-detail{
  flex:1;
  color: #333;
  padding-right: 40rpx;
}


.product-detail-protect view{
  font-size: 24rpx;
  color:808080;
  text-align: center;
  margin-top: 30rpx;
}

product.js

// var productObj = require('product-model.js');

import {Product} from 'product-model.js';
import {Cart} from '../cart/cart-model.js';

var product=new Product();  //實例化 商品詳情 對象
var cart=new Cart();
Page({
    data: {
        loadingHidden:false,
        hiddenSmallImg:true,
        countsArray:[1,2,3,4,5,6,7,8,9,10],
        productCounts:1,
        currentTabsIndex:0,
        cartTotalCounts:0,
    },
    onLoad: function (option) {
        var id = option.id;
        this.data.id=id;
        this._loadData();
    },

    /*加載所有數據*/
    _loadData:function(callback){
        var that = this;
        product.getDetailInfo(this.data.id,(data)=>{
            that.setData({
                cartTotalCounts:cart.getCartTotalCounts().counts1,
                product:data,
                loadingHidden:true
            });
            callback&& callback();
        });
    },

    //選擇購買數目
    bindPickerChange: function(e) {
        this.setData({
            productCounts: this.data.countsArray[e.detail.value],
        })
    },

    //切換詳情面板
    onTabsItemTap:function(event){
        var index=product.getDataSet(event,'index');
        this.setData({
            currentTabsIndex:index
        });
    },

    /*添加到購物車*/
    onAddingToCartTap:function(events){
        //防止快速點擊
        if(this.data.isFly){
            return;
        }
        this._flyToCartEffect(events);
        this.addToCart();
    },

    /*將商品數據添加到內存中*/
    addToCart:function(){
        var tempObj={},keys=['id','name','main_img_url','price'];
        for(var key in this.data.product){
            if(keys.indexOf(key)>=0){
                tempObj[key]=this.data.product[key];
            }
        }

        cart.add(tempObj,this.data.productCounts);
    },

    /*加入購物車動效*/
    _flyToCartEffect:function(events){
        //獲得當前點擊的位置,距離可視區域左上角
        var touches=events.touches[0];
        var diff={
                x:'25px',
                y:25-touches.clientY+'px'
            },
            style='display: block;-webkit-transform:translate('+diff.x+','+diff.y+') rotate(350deg) scale(0)';  //移動距離
        this.setData({
            isFly:true,
            translateStyle:style
        });
        var that=this;
        setTimeout(()=>{
            that.setData({
                isFly:false,
                translateStyle:'-webkit-transform: none;',  //恢復到最初狀態
                isShake:true,
            });
            setTimeout(()=>{
            //頁面數量的動態綁定!!!!初始化的數據加上用戶點擊時的數據相加
                var counts=that.data.cartTotalCounts+that.data.productCounts;
                that.setData({
                    isShake:false,
                    cartTotalCounts:counts
                });
            },200);
        },1000);
    },

    /*跳轉到購物車*/
    onCartTap:function(){
        wx.switchTab({
            url: '/pages/cart/cart'
        });
    },

    /*下拉刷新頁面*/
    onPullDownRefresh: function(){
        this._loadData(()=>{
            wx.stopPullDownRefresh()
        });
    },

    //分享效果
    onShareAppMessage: function () {
        return {
            title: '零食商販 Pretty Vendor',
            path: 'pages/product/product?id=' + this.data.id
        }
    }

})

分類頁面wxml

<import src="../tpls/category/category-tpl.wxml"/>
<view class="container category-container">
  <view class="category-box">
      <view class="left-box">
        <block wx:for="{{categoryTypeArr}}">
          <view class="menu-item {{currentMenuIndex==index?'selected':''}}" data-id="{{item.id}}" data-index="{{index}}" bindtap="changeCategory" data-title-name="{{item.name}}">
            {{item.name}}
          </view>
        </block>
    </view>
    <view class="right-box {{transClassArr[currentMenuIndex]}}">

      <view class="foods-type-box">
        <template is="categorydetail" data="{{categoryInfo:categoryInfo0}}"/>
      </view>
      <view class="foods-type-box">
        <template is="categorydetail" data="{{categoryInfo:categoryInfo1}}"/>
      </view>
      <view class="foods-type-box">
        <template is="categorydetail" data="{{categoryInfo:categoryInfo2}}"/>
      </view>
      <view class="foods-type-box">
        <template is="categorydetail" data="{{categoryInfo:categoryInfo3}}"/>
      </view>
      <view class="foods-type-box">
        <template is="categorydetail" data="{{categoryInfo:categoryInfo4}}"/>
      </view>
      <view class="foods-type-box">
        <template is="categorydetail" data="{{categoryInfo:categoryInfo5}}"/>
      </view>

    </view>

  </view>
  <loading hidden="{{loadingHidden}}">
    加載中...
  </loading>
</view>

分類頁面js

import { Category } from 'category-model.js';
var category=new Category();  //實例化 home 的推薦頁面
Page({
  data: {
    transClassArr:['tanslate0','tanslate1','tanslate2','tanslate3','tanslate4','tanslate5'],
    currentMenuIndex:0,
    loadingHidden:false,
  },
  onLoad: function () {
    this._loadData();
  },

  /*加載所有數據*/
  _loadData:function(callback){
    var that = this;
    category.getCategoryType((categoryData)=>{

      that.setData({
        categoryTypeArr: categoryData,
        loadingHidden: true
      });

      that.getProductsByCategory(categoryData[0].id,(data)=>{
        var dataObj= {
          procucts: data,
          topImgUrl: categoryData[0].img.url,
          title: categoryData[0].name
        };
        that.setData({
          loadingHidden: true,
          categoryInfo0:dataObj
        });
        callback&& callback();
      });
    });
  },

  /*切換分類*/
  changeCategory:function(event){
    var index=category.getDataSet(event,'index'),
        id=category.getDataSet(event,'id')//獲取data-set
    this.setData({
      currentMenuIndex:index
    });

    //如果數據是第一次請求
    if(!this.isLoadedData(index)) {
      var that=this;
      this.getProductsByCategory(id, (data)=> {
        that.setData(that.getDataObjForBind(index,data));
      });
    }
  },

  isLoadedData:function(index){
    if(this.data['categoryInfo'+index]){
      return true;
    }
    return false;
  },

  getDataObjForBind:function(index,data){
    var obj={},
        arr=[0,1,2,3,4,5],
        baseData=this.data.categoryTypeArr[index];
    for(var item in arr){
      if(item==arr[index]) {
        obj['categoryInfo' + item]={
          procucts:data,
          topImgUrl:baseData.img.url,
          title:baseData.name
        };

        return obj;
      }
    }
  },

  getProductsByCategory:function(id,callback){
    category.getProductsByCategory(id,(data)=> {
      callback&&callback(data);
    });
  },

  /*跳轉到商品詳情*/
  onProductsItemTap: function (event) {
    var id = category.getDataSet(event, 'id');
    wx.navigateTo({
      url: '../product/product?id=' + id
    })
  },

  /*下拉刷新頁面*/
  onPullDownRefresh: function(){
    this._loadData(()=>{
      wx.stopPullDownRefresh()
    });
  },

  //分享效果
  onShareAppMessage: function () {
    return {
      title: '零食商販 Pretty Vendor',
      path: 'pages/category/category'
    }
  }

})

分類頁面css

@import "../tpls/category/category-tpl.wxss";
.category-container{
    /*min-height: 100vh;*/
}
.category-box{
    display: flex;
    height: 100vh;
    overflow: hidden;
}
.left-box{
    flex: 0 0 150rpx;
    border-right:1rpx solid #D8D8D8;
}
.menu-item{
    height: 50rpx;
    padding: 20rpx 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size:28rpx;
    border-left: 6rpx solid #fff;
}
.menu-item.selected{
    color: #AB956D;
    border-left-color:#AB956D;
}

.right-box{
    flex: 1;
    transition: all 500ms ease-in-out;
}
.tanslate0{
    transform:translate(0,0);
}
.tanslate1{
    transform:translate(0,-100vh);
}
.tanslate2{
    transform:translate(0,-200vh);
}
.tanslate3{
    transform:translate(0,-300vh);
}
.tanslate4{
    transform:translate(0,-400vh);
}
.tanslate5{
    transform:translate(0,-500vh);
}
.foods-type-box{
    height: 100vh;
}

購物車模塊,數據存到本地,當用戶選中某些商品下單購買時,會從緩存中刪除該數據,更新緩存,當用用戶全部購買時,直接刪除整個緩存,首先就是添加到購物車的方法,如果之前沒有樣的商品,則直接添加一條新的記錄, 數量爲 counts,如果有,則只將相應數量 + counts,有兩個小技巧,第一個檢測購物車裏是否有該商品,第二個是通過對象的KEY拿到自己想要的數據格式。

檢測購物車裏是否有該商品

/*購物車中是否已經存在該商品*/
    _isHasThatOne(id,arr){
        var item,
            result={index:-1};
        for(let i=0;i<arr.length;i++){
            item=arr[i];
            if(item.id==id) {
                result = {
                    index:i,
                    data:item
                };
                break;
            }
        }
        return result;
    }

通過對象的KEY拿到自己想要的數據格式

addToCart:function(){
        var tempObj={},keys=['id','name','main_img_url','price'];
        for(var key in this.data.product){
            if(keys.indexOf(key)>=0){
                tempObj[key]=this.data.product[key];
            }
        }
        cart.add(tempObj,this.data.productCounts);
    },

防止浮點數運算出現誤差

/*
    * 計算總金額和選擇的商品總數
    * */
    _calcTotalAccountAndCounts:function(data){
        var len=data.length,
            account=0,
            selectedCounts=0,
            selectedTypeCounts=0;
        let multiple=100;
        for(let i=0;i<len;i++){
            //避免 0.05 + 0.01 = 0.060 000 000 000 000 005 的問題,乘以 100 *100
            if(data[i].selectStatus) {
                account += data[i].counts * multiple *  Number(data[i].price)*multiple;
                selectedCounts+=data[i].counts;
                selectedTypeCounts++;
            }
        }
        return{
            selectedCounts:selectedCounts,
            selectedTypeCounts:selectedTypeCounts,
            account:account/(multiple*multiple)
        }
    },

購物車頁面cart-model.js

順勢掌握小程序緩存數據的方法

/**
 * Created by jimmy on 17/03/05.
 */
import {Base} from '../../utils/base.js';

/*
* 購物車數據存放在本地,
* 當用戶選中某些商品下單購買時,會從緩存中刪除該數據,更新緩存
* 當用用戶全部購買時,直接刪除整個緩存
*
*/
class Cart extends Base{
    constructor(){
        super();
        this._storageKeyName='cart';
    };

    /*
    * 獲取購物車
    * param
    * flag - {bool} 是否過濾掉不下單的商品
    */
    getCartDataFromLocal(flag){
        var res = wx.getStorageSync(this._storageKeyName);
        if(!res){
            res=[];
        }
        //在下單的時候過濾不下單的商品,
        if(flag){
            var newRes=[];
            for(let i=0;i<res.length;i++){
                if(res[i].selectStatus){
                    newRes.push(res[i]);
                }
            }
            res=newRes;
        }

        return res;
    };

    /*
    *獲得購物車商品總數目,包括分類和不分類
    * param:
    * flag - {bool} 是否區分選中和不選中
    * return
    * counts1 - {int} 不分類
    * counts2 -{int} 分類
    */
    getCartTotalCounts(flag){
        var data=this.getCartDataFromLocal(),
            counts1=0,
            counts2=0;
        for(let i=0;i<data.length;i++){
            if (flag){
                if(data[i].selectStatus) {
                    counts1 += data[i].counts;
                    counts2++;
                }
            }else{
                counts1 += data[i].counts;
                counts2++;
            }
        }
        return {
            counts1:counts1,
            counts2:counts2
        };
    };

    /*本地緩存 保存/更新*/
    execSetStorageSync(data){
        wx.setStorageSync(this._storageKeyName,data);
    };


    /*
    * 加入到購物車
    * 如果之前沒有樣的商品,則直接添加一條新的記錄, 數量爲 counts
    * 如果有,則只將相應數量 + counts
    * @params:
    * item - {obj} 商品對象,
    * counts - {int} 商品數目,
    * */
    add(item,counts){
        var cartData=this.getCartDataFromLocal();
        if(!cartData){
            cartData=[];
        }
        var isHadInfo=this._isHasThatOne(item.id,cartData);
        //新商品
        if(isHadInfo.index==-1) {
            item.counts=counts;
            item.selectStatus=true;  //默認在購物車中爲選中狀態
            cartData.push(item);
        }
        //已有商品
        else{
            cartData[isHadInfo.index].counts+=counts;
        }
        this.execSetStorageSync(cartData);  //更新本地緩存
        return cartData;
    };

    /*
    * 修改商品數目
    * params:
    * id - {int} 商品id
    * counts -{int} 數目
    * */
    _changeCounts(id,counts){
        var cartData=this.getCartDataFromLocal(),
            hasInfo=this._isHasThatOne(id,cartData);
        if(hasInfo.index!=-1){
            if(hasInfo.data.counts>1){
                cartData[hasInfo.index].counts+=counts;
            }
        }
        this.execSetStorageSync(cartData);  //更新本地緩存
    };

    /*
    * 增加商品數目
    * */
    addCounts(id){
        this._changeCounts(id,1);
    };

    /*
    * 購物車減
    * */
    cutCounts(id){
        this._changeCounts(id,-1);
    };

    /*購物車中是否已經存在該商品*/
    _isHasThatOne(id,arr){
        var item,
            result={index:-1};
        for(let i=0;i<arr.length;i++){
            item=arr[i];
            if(item.id==id) {
                result = {
                    index:i,
                    data:item
                };
                break;
            }
        }
        return result;
    }

    /*
    * 刪除某些商品
    */
    delete(ids){
        if(!(ids instanceof Array)){
            ids=[ids];
        }
        var cartData=this.getCartDataFromLocal();
        for(let i=0;i<ids.length;i++) {
            var hasInfo = this._isHasThatOne(ids[i], cartData);
            if (hasInfo.index != -1) {
                cartData.splice(hasInfo.index, 1);  //刪除數組某一項
            }
        }
        this.execSetStorageSync(cartData);
    }
}

export {Cart};

cart.js

// var CartObj = require('cart-model.js');

import {Cart} from 'cart-model.js';

var cart=new Cart(); //實例化 購物車
var x1=0;
var x2=0;

Page({
    data: {
        loadingHidden:false,
        selectedCounts:0, //總的商品數
        selectedTypeCounts:0, //總的商品類型數
    },

    onLoad: function () {

    },

    /*
     * 頁面重新渲染,包括第一次,和onload方法沒有直接關係
     */
    onShow:function(){
        var cartData=cart.getCartDataFromLocal(),
            countsInfo=cart.getCartTotalCounts(true);
        this.setData({
            selectedCounts:countsInfo.counts1,
            selectedTypeCounts:countsInfo.counts2,
            account:this._calcTotalAccountAndCounts(cartData).account,
            loadingHidden:true,
            cartData:cartData
        });
    },

    /*離開頁面時,更新本地緩存*/
    onHide:function(){
        cart.execSetStorageSync(this.data.cartData);
    },

    /*更新購物車商品數據*/
    _resetCartData:function(){
        var newData = this._calcTotalAccountAndCounts(this.data.cartData); /*重新計算總金額和商品總數*/
        this.setData({
            account: newData.account,
            selectedCounts:newData.selectedCounts,
            selectedTypeCounts:newData.selectedTypeCounts,
            cartData:this.data.cartData
        });
    },

    /*
    * 計算總金額和選擇的商品總數
    * */
    _calcTotalAccountAndCounts:function(data){
        var len=data.length,
            account=0,
            selectedCounts=0,
            selectedTypeCounts=0;
        let multiple=100;
        for(let i=0;i<len;i++){
            //避免 0.05 + 0.01 = 0.060 000 000 000 000 005 的問題,乘以 100 *100
            if(data[i].selectStatus) {
                account += data[i].counts * multiple *  Number(data[i].price)*multiple;
                selectedCounts+=data[i].counts;
                selectedTypeCounts++;
            }
        }
        return{
            selectedCounts:selectedCounts,
            selectedTypeCounts:selectedTypeCounts,
            account:account/(multiple*multiple)
        }
    },


    /*調整商品數目*/
    changeCounts:function(event){
        var id=cart.getDataSet(event,'id'),
            type=cart.getDataSet(event,'type'),
            index=this._getProductIndexById(id),
            counts=1;
        if(type=='add') {
            cart.addCounts(id);
        }else{
            counts=-1;
            cart.cutCounts(id);
        }
        //更新商品頁面
        this.data.cartData[index].counts+=counts;
        this._resetCartData();
    },

    /*根據商品id得到 商品所在下標*/
    _getProductIndexById:function(id){
        var data=this.data.cartData,
            len=data.length;
        for(let i=0;i<len;i++){
            if(data[i].id==id){
                return i;
            }
        }
    },

    /*刪除商品*/
    delete:function(event){
        var id=cart.getDataSet(event,'id'),
        index=this._getProductIndexById(id);
        this.data.cartData.splice(index,1);//刪除某一項商品

        this._resetCartData();
        //this.toggleSelectAll();

        cart.delete(id);  //內存中刪除該商品
    },

    /*選擇商品*/
    toggleSelect:function(event){
        var id=cart.getDataSet(event,'id'),
            status=cart.getDataSet(event,'status'),
            index=this._getProductIndexById(id);
        this.data.cartData[index].selectStatus=!status;
        this._resetCartData();
    },

    /*全選*/
    toggleSelectAll:function(event){
        var status=cart.getDataSet(event,'status')=='true';
        var data=this.data.cartData,
            len=data.length;
        for(let i=0;i<len;i++) {
            data[i].selectStatus=!status;
        }
        this._resetCartData();
    },

    /*提交訂單*/
    submitOrder:function(){
        wx.navigateTo({
            url:'../order/order?account='+this.data.account+'&from=cart'
        });
    },

    /*查看商品詳情*/
    onProductsItemTap:function(event){
        var id = cart.getDataSet(event, 'id');
        wx.navigateTo({
            url: '../product/product?id=' + id
        })
    }


})

cart.json

{
  "navigationBarTitleText": "購物車",
  "enablePullDownRefresh":false
}

cart.wxml

<view class="container cart-container">
    <block wx:if="{{cartData.length>0}}">
        <view class="cart-box">
            <block wx:for="{{cartData}}">
                <view class="cart-item {{deleteFlag&&index==currentIndex?'showDeleteBtn':'hideDeleteBtn'}}">
                    <view class="cart-item-main" data-id="{{item.id}}" data-index="{{index}}">
                        <view   class="cart-item-checkbox" ontap="toggleSelect" data-id="{{item.id}}" data-status="{{item.selectStatus}}">
                            <image wx:if="{{item.selectStatus}}" src="../../imgs/icon/[email protected]"></image>
                            <image wx:else src="../../imgs/icon/[email protected]"></image>
                        </view>
                        <view class="cart-item-img" bindtap="onProductsItemTap" data-id="{{item.id}}">
                            <image class="good-image" src="{{item.main_img_url}}"></image>
                        </view>
                        <view class="cart-item-word">
                            <view class="title-box">
                                <text class="title">{{item.name}}</text>
                                <text>¥{{item.price}}</text>
                            </view>
                            <view class="bottom-box">
                                <view class="cart-item-counts">
                                    <view class="btns {{item.counts==1?'disabled':''}}" bindtap="changeCounts" data-id="{{item.id}}" data-type="cut">-</view>
                                    <view class="counts">{{item.counts}}</view>
                                    <view class="btns" bindtap="changeCounts" data-id="{{item.id}}" data-type="add">+</view>
                                </view>
                                <view class="delete" data-id="{{item.id}}" bindtap="delete">×</view>
                            </view>
                        </view>
                    </view>
                </view>
            </block>
        </view>
        <view class="footer-account-box all-accounts-box">
            <view class="all-select" ontap="toggleSelectAll" data-status="{{selectedTypeCounts==cartData.length?'true':'false'}}">
                <image wx:if="{{selectedTypeCounts==cartData.length}}"
                       class="title-icon" src="../../imgs/icon/[email protected]"></image>
                <image wx:else class="title-icon" src="../../imgs/icon/all.png"></image>
                <text>全選({{selectedCounts}})</text>
            </view>
            <view class="all-price-submit {{account==0?'disabled':''}}" bindtap="submitOrder">
                <view class="accounts-btn">下單</view>
                <view class="price-text">¥{{account}}</view>
                <view class="arrow-icon">
                    <image wx:if="{{account==0}}" src="../../imgs/icon/[email protected]"></image>
                    <image wx:else src="../../imgs/icon/arrow.png"></image>
                </view>
            </view>
        </view>
    </block>
    <view  wx:else class="no-data">
        您還沒有添加任何商品
    </view>
    <loading hidden="{{loadingHidden}}">
        加載中...
    </loading>
</view>

真實的項目中,購物車這樣的數據是不會放在緩存裏的,本課程通過購物車運用小程序的本地緩存能力,以及技巧

刪除,支持批量刪除。傳入一個數組,通過遍歷方法依次刪除

結合生命週期onhide方法更新本地緩存,以及如何過濾掉不下單的商品,cart.model.js展示了前端和數據庫的交互,只暴露方法,方法的詳細實現過程不暴露

根據小程序的特性,學習課程調整商品數量的方法,一個方法實現兩種操作

再次強調this的指向,在回調函數this的指向發生變化只有使用箭頭函數才能保證this的指向。

此課程對於訂單模塊比較精簡,與實際開發過程不一致,僅供參考。

order-model.js

/**
 * Created by jimmy on 17/03/09.
 */

import {Base} from '../../utils/base.js'

class Order extends Base{

    constructor(){
        super();
        this._storageKeyName='newOrder';
    }

    /*下訂單*/
    doOrder(param,callback){
        var that=this;
        var allParams = {
            url: 'order',
            type:'post',
            data:{products:param},
            sCallback: function (data) {
                that.execSetStorageSync(true);
                callback && callback(data);
            },
            eCallback:function(){
                }
            };
        this.request(allParams);
    }

    /*
    * 拉起微信支付
    * params:
    * norderNumber - {int} 訂單id
    * return:
    * callback - {obj} 回調方法 ,返回參數 可能值 0:商品缺貨等原因導致訂單不能支付;  1: 支付失敗或者支付取消; 2:支付成功;
    * */
    execPay(orderNumber,callback){
        var allParams = {
            url: 'pay/pre_order',
            type:'post',
            data:{id:orderNumber},
            sCallback: function (data) {
                var timeStamp= data.timeStamp;
                if(timeStamp) { //可以支付
                    wx.requestPayment({
                        'timeStamp': timeStamp.toString(),
                        'nonceStr': data.nonceStr,
                        'package': data.package,
                        'signType': data.signType,
                        'paySign': data.paySign,
                        success: function () {
                            callback && callback(2);
                        },
                        fail: function () {
                            callback && callback(1);
                        }
                    });
                }else{
                    callback && callback(0);
                }
            }
        };
        this.request(allParams);
    }

    /*獲得所有訂單,pageIndex 從1開始*/
    getOrders(pageIndex,callback){
        var allParams = {
            url: 'order/by_user',
            data:{page:pageIndex},
            type:'get',
            sCallback: function (data) {
                callback && callback(data);  //1 未支付  2,已支付  3,已發貨,4已支付,但庫存不足
             }
        };
        this.request(allParams);
    }

    /*獲得訂單的具體內容*/
    getOrderInfoById(id,callback){
        var that=this;
        var allParams = {
            url: 'order/'+id,
            sCallback: function (data) {
                callback &&callback(data);
            },
            eCallback:function(){

            }
        };
        this.request(allParams);
    }

    /*本地緩存 保存/更新*/
    execSetStorageSync(data){
        wx.setStorageSync(this._storageKeyName,data);
    };

    /*是否有新的訂單*/
    hasNewOrder(){
       var flag = wx.getStorageSync(this._storageKeyName);
       return flag==true;
    }

}

export {Order};

order.js

import {Order} from '../order/order-model.js';
import {Cart} from '../cart/cart-model.js';
import {Address} from '../../utils/address.js';

var order=new Order();
var cart=new Cart();
var address=new Address();

Page({
        data: {
            fromCartFlag:true,
            addressInfo:null
        },

        /*
        * 訂單數據來源包括兩個:
        * 1.購物車下單
        * 2.舊的訂單
        * */
        onLoad: function (options) {
            var flag=options.from=='cart',
                that=this;
            this.data.fromCartFlag=flag;
            this.data.account=options.account;

            //來自於購物車
            if(flag) {
                this.setData({
                    productsArr: cart.getCartDataFromLocal(true),
                    account:options.account,
                    orderStatus:0
                });

                /*顯示收穫地址*/
                address.getAddress((res)=> {
                    that._bindAddressInfo(res);
                });
            }

            //舊訂單
            else{
                this.data.id=options.id;
            }
        },

        onShow:function(){
            if(this.data.id) {
                var that = this;
                //下單後,支付成功或者失敗後,點左上角返回時能夠更新訂單狀態 所以放在onshow中
                var id = this.data.id;
                order.getOrderInfoById(id, (data)=> {
                    that.setData({
                        orderStatus: data.status,
                        productsArr: data.snap_items,
                        account: data.total_price,
                        basicInfo: {
                            orderTime: data.create_time,
                            orderNo: data.order_no
                        },
                    });

                    // 快照地址
                    var addressInfo=data.snap_address;
                    addressInfo.totalDetail = address.setAddressInfo(addressInfo);
                    that._bindAddressInfo(addressInfo);
                });
            }
        },

        /*修改或者添加地址信息*/
        editAddress:function(){
            var that=this;
            wx.chooseAddress({
                success: function (res) {
                    var addressInfo = {
                        name:res.userName,
                        mobile:res.telNumber,
                        totalDetail:address.setAddressInfo(res)
                    };
                    that._bindAddressInfo(addressInfo);

                    //保存地址
                    address.submitAddress(res,(flag)=>{
                        if(!flag) {
                            that.showTips('操作提示','地址信息更新失敗!');
                        }
                    });
                }
            })
        },

        /*綁定地址信息*/
        _bindAddressInfo:function(addressInfo){
            this.setData({
                addressInfo: addressInfo
            });
        },

        /*下單和付款*/
        pay:function(){
            if(!this.data.addressInfo){
                this.showTips('下單提示','請填寫您的收貨地址');
                return;
            }
            if(this.data.orderStatus==0){
                this._firstTimePay();
            }else{
                this._oneMoresTimePay();
            }
        },

        /*第一次支付*/
        _firstTimePay:function(){
            var orderInfo=[],
                procuctInfo=this.data.productsArr,
                order=new Order();
            for(let i=0;i<procuctInfo.length;i++){
                orderInfo.push({
                    product_id:procuctInfo[i].id,
                    count:procuctInfo[i].counts
                });
            }

            var that=this;
            //支付分兩步,第一步是生成訂單號,然後根據訂單號支付
            order.doOrder(orderInfo,(data)=>{
                //訂單生成成功
                if(data.pass) {
                    //更新訂單狀態
                    var id=data.order_id;
                    that.data.id=id;
                    that.data.fromCartFlag=false;

                    //開始支付
                    that._execPay(id);
                }else{
                    that._orderFail(data);  // 下單失敗
                }
            });
        },


        /*
        * 提示窗口
        * params:
        * title - {string}標題
        * content - {string}內容
        * flag - {bool}是否跳轉到 "我的頁面"
        */
        showTips:function(title,content,flag){
            wx.showModal({
                title: title,
                content: content,
                showCancel:false,
                success: function(res) {
                    if(flag) {
                        wx.switchTab({
                            url: '/pages/my/my'
                        });
                    }
                }
            });
        },

        /*
        *下單失敗
        * params:
        * data - {obj} 訂單結果信息
        * */
        _orderFail:function(data){
            var nameArr=[],
                name='',
                str='',
                pArr=data.pStatusArray;
            for(let i=0;i<pArr.length;i++){
                if(!pArr[i].haveStock){
                    name=pArr[i].name;
                    if(name.length>15){
                        name = name.substr(0,12)+'...';
                    }
                    nameArr.push(name);
                    if(nameArr.length>=2){
                        break;
                    }
                }
            }
            str+=nameArr.join('、');
            if(nameArr.length>2){
                str+=' 等';
            }
            str+=' 缺貨';
            wx.showModal({
                title: '下單失敗',
                content: str,
                showCancel:false,
                success: function(res) {

                }
            });
        },

        /* 再次次支付*/
        _oneMoresTimePay:function(){
            this._execPay(this.data.id);
        },

        /*
        *開始支付
        * params:
        * id - {int}訂單id
        */
        _execPay:function(id){
            if(!order.onPay) {
                this.showTips('支付提示','本產品僅用於演示,支付系統已屏蔽',true);//屏蔽支付,提示
                this.deleteProducts(); //將已經下單的商品從購物車刪除
                return;
            }
            var that=this;
            order.execPay(id,(statusCode)=>{
                if(statusCode!=0){
                    that.deleteProducts(); //將已經下單的商品從購物車刪除   當狀態爲0時,表示

                    var flag = statusCode == 2;
                    wx.navigateTo({
                        url: '../pay-result/pay-result?id=' + id + '&flag=' + flag + '&from=order'
                    });
                }
            });
        },

        //將已經下單的商品從購物車刪除
        deleteProducts:function() {
            var ids=[],arr=this.data.productsArr;
            for(let i=0;i<arr.length;i++){
                ids.push(arr[i].id);
            }
            cart.delete(ids);
        },


    }
)

order.json

{
  "navigationBarTitleText": "訂單詳情",
  "enablePullDownRefresh":false
}

order.wxml

<!--訂單詳情-->
<view class="container order-container">
  <!--訂單編號和下單時間,如果是舊訂單就顯示-->
  <view class="order-basic-info" wx:if="{{basicInfo}}">
    <view class="order-time-no">
      <view>
        <text class="key">下單時間:</text>
        <text class="val">{{basicInfo.orderTime}}</text>
      </view>
      <view>
        <text class="key">訂單編號:</text>
        <text class="order-no-txt val">{{basicInfo.orderNo}}</text>
      </view>
    </view>
    <view class="order-status">
      <text class="order-status-txt unpay" wx:if="{{orderStatus==1}}">待付款</text>
      <text class="order-status-txt payed" wx:if="{{orderStatus==2}}">已付款</text>
      <text class="order-status-txt done" wx:if="{{orderStatus==3}}">已發貨</text>
    </view>
  </view>

  <!--地址-->
  <view class="order-address-info {{orderStatus!=0?'disabled':''}}" ontap="editAddress">
    <block wx:if="{{addressInfo}}">
      <view class="contact-box">
        <view>
          <view class="contact">
              <view>
                <image src="../../imgs/icon/user.png"></image>
                <text class="val">{{addressInfo.name}}</text>
              </view>
              <view class="mobile-box">
                <image src="../../imgs/icon/mobile.png"></image>
                <text  class="val">{{addressInfo.mobile}}</text>
              </view>
          </view>
          <view class="detail">{{addressInfo.totalDetail}}</view>
        </view>
        <view class="contact-icon" wx:if="{{orderStatus==0}}">
            <image src="../../imgs/icon/[email protected]"></image>
        </view>
      </view>
    </block>
    <block wx:else>
      <view class="add-new-address">
        <text class="icon">+</text>
        <text>添加地址</text></view>
    </block>
  </view>

  <!--列表-->
  <view class="order-main">
    <block wx:for="{{productsArr}}">
      <view class="product-item">
        <view class="item-left">
          <image src="{{item.main_img_url}}"></image>
        </view>
        <view class="item-middle">
          <view>{{item.name}}</view>
          <view>{{item.price}}</view>
        </view>
        <view class="item-right">
          ×{{item.counts}}
        </view>
      </view>
    </block>
    </view>

  <!--結算-->
  <view class="footer-account-box order-accounts">
    <view class="total-account">
      付款合計:¥{{account}}
    </view>
    <view wx:if="{{orderStatus<=1}}" class="pay {{!addressInfo?'disabled':''}}" ontap="pay">去付款</view>
  </view>
</view>

我的頁面

涉及獲取用戶信息

my-model.js

/**
 * Created by jimmy on 17/3/24.
 */
import {Base} from '../../utils/base.js'

class My extends Base{
    constructor(){
        super();
    }

    //得到用戶信息
    getUserInfo(cb){
        var that=this;
        wx.login({
            success: function () {
                wx.getUserInfo({
                    success: function (res) {
                        typeof cb == "function" && cb(res.userInfo);

                        //將用戶暱稱 提交到服務器
                        if(!that.onPay) {
                            that._updateUserInfo(res.userInfo);
                        }

                    },
                    fail:function(res){
                        typeof cb == "function" && cb({
                            avatarUrl:'../../imgs/icon/[email protected]',
                            nickName:'零食小販'
                        });
                    }
                });
            },

        })
    }

    /*更新用戶信息到服務器*/
    _updateUserInfo(res){
        var nickName=res.nickName;
        delete res.avatarUrl;  //將暱稱去除
        delete res.nickName;  //將暱稱去除
        var allParams = {
            url: 'user/wx_info',
            data:{nickname:nickName,extend:JSON.stringify(res)},
            type:'post',
            sCallback: function (data) {
            }
        };
        this.request(allParams);

    }
}
export {My}

my.js

訂單列表分頁,特別注意分頁數據合併方法

import {Address} from '../../utils/address.js';
import {Order} from '../order/order-model.js';
import {My} from '../my/my-model.js';

var address=new Address();
var order=new Order();
var my=new My();

Page({
    data: {
        pageIndex:1,
        isLoadedAll:false,
        loadingHidden:false,
        orderArr:[],
        addressInfo:null
    },
    onLoad:function(){
        this._loadData();
        this._getAddressInfo();
    },

    onShow:function(){
        //更新訂單,相當自動下拉刷新,只有  非第一次打開 “我的”頁面,且有新的訂單時 才調用。
        var newOrderFlag=order.hasNewOrder();
        if(this.data.loadingHidden &&newOrderFlag){
            this.onPullDownRefresh();
        }
    },

    _loadData:function(){
        var that=this;
        my.getUserInfo((data)=>{
            that.setData({
                userInfo:data
            });

        });

        this._getOrders();
        order.execSetStorageSync(false);  //更新標誌位
    },

    /**地址信息**/
    _getAddressInfo:function(){
        var that=this;
        address.getAddress((addressInfo)=>{
            that._bindAddressInfo(addressInfo);
        });
    },

    /*修改或者添加地址信息*/
    editAddress:function(){
        var that=this;
        wx.chooseAddress({
            success: function (res) {
                var addressInfo = {
                    name:res.userName,
                    mobile:res.telNumber,
                    totalDetail:address.setAddressInfo(res)
                };
                if(res.telNumber) {
                    that._bindAddressInfo(addressInfo);
                    //保存地址
                    address.submitAddress(res, (flag)=> {
                        if (!flag) {
                            that.showTips('操作提示', '地址信息更新失敗!');
                        }
                    });
                }
                //模擬器上使用
                else{
                    that.showTips('操作提示', '地址信息更新失敗,手機號碼信息爲空!');
                }
            }
        })
    },

    /*綁定地址信息*/
    _bindAddressInfo:function(addressInfo){
        this.setData({
            addressInfo: addressInfo
        });
    },

    /*訂單信息*/
    _getOrders:function(callback){
        var that=this;
        order.getOrders(this.data.pageIndex,(res)=>{
            var data=res.data;
            that.setData({
                loadingHidden: true
            });
            if(data.length>0) {
                that.data.orderArr.push.apply(that.data.orderArr,res.data);  //數組合並
                that.setData({
                    orderArr: that.data.orderArr
                });
            }else{
                that.data.isLoadedAll=true;  //已經全部加載完畢
                that.data.pageIndex=1;
            }
            callback && callback();
        });
    },

    /*顯示訂單的具體信息*/
    showOrderDetailInfo:function(event){
        var id=order.getDataSet(event,'id');
        wx.navigateTo({
            url:'../order/order?from=order&id='+id
        });
    },

    /*未支付訂單再次支付*/
    rePay:function(event){
        var id=order.getDataSet(event,'id'),
            index=order.getDataSet(event,'index');

        //online 上線實例,屏蔽支付功能
        if(order.onPay) {
            this._execPay(id,index);
        }else {
            this.showTips('支付提示','本產品僅用於演示,支付系統已屏蔽');
        }
    },

    /*支付*/
    _execPay:function(id,index){
        var that=this;
        order.execPay(id,(statusCode)=>{
            if(statusCode>0){
                var flag=statusCode==2;

                //更新訂單顯示狀態
                if(flag){
                    that.data.orderArr[index].status=2;
                    that.setData({
                        orderArr: that.data.orderArr
                    });
                }

                //跳轉到 成功頁面
                wx.navigateTo({
                    url: '../pay-result/pay-result?id='+id+'&flag='+flag+'&from=my'
                });
            }else{
                that.showTips('支付失敗','商品已下架或庫存不足');
            }
        });
    },

    /*下拉刷新頁面*/
    onPullDownRefresh: function(){
        var that=this;
        this.data.orderArr=[];  //訂單初始化
        this._getOrders(()=>{
            that.data.isLoadedAll=false;  //是否加載完全
            that.data.pageIndex=1;
            wx.stopPullDownRefresh();
            order.execSetStorageSync(false);  //更新標誌位
        });
    },


    onReachBottom:function(){
        if(!this.data.isLoadedAll) {
            this.data.pageIndex++;
            this._getOrders();
        }
    },

    /*
     * 提示窗口
     * params:
     * title - {string}標題
     * content - {string}內容
     * flag - {bool}是否跳轉到 "我的頁面"
     */
    showTips:function(title,content){
        wx.showModal({
            title: title,
            content: content,
            showCancel:false,
            success: function(res) {

            }
        });
    },

})

my.wxml


<view class="container my-container" hidden="{{!loadingHidden}}">
  <view class="my-header">
    <image src="{{userInfo.avatarUrl}}"></image>
    <text class="name">{{userInfo.nickName}}</text>
  </view>
  <!--地址管理-->
  <view class="my-address">
    <block wx:if="{{addressInfo}}">
      <view class="item-title" ontap="editAddress">
        地址管理
        <image src="../../imgs/icon/[email protected]"></image>
      </view>
      <view  class="item-main">
          <view class="section">
            <input disabled name="name" placeholder="姓名" value="{{addressInfo.name}}" />
          </view>
          <view class="section">
            <input disabled type="number" name="mobile" placeholder="手機號碼" value="{{addressInfo.mobile}}"/>
          </view>
          <view class="section">
            <input disabled name="detail" placeholder="收貨地址" value="{{addressInfo.totalDetail}}"/>
          </view>
      </view>
    </block>
    <block wx:else>
      <view class="add-new-address" ontap="editAddress"><text class="icon">+</text><text>添加地址</text></view>
    </block>
  </view>

  <view class="my-order">
    <view class="item-title">我的訂單</view>
    <view class="item-main">
      <block wx:for="{{orderArr}}">
        <view class="order-item">
          <view class="order-header" ontap="showOrderDetailInfo" data-id="{{item.id}}">
            <text>訂單編號:</text>
            <text class="order-no-txt">{{item.order_no}}</text>
          </view>
          <view class="order-main" ontap="showOrderDetailInfo" data-id="{{item.id}}">
            <view class="item-left">
              <image src="{{item.snap_img}}"></image>
            </view>
            <view class="item-middle">
              <view>{{item.snap_name}}</view>
              <view>{{item.total_count}}件商品</view>
            </view>
            <view class="item-right">
              <text class="order-status-txt unpay" wx:if="{{item.status==1}}">待付款</text>
              <text class="order-status-txt payed" wx:if="{{item.status==2}}">已付款</text>
              <text class="order-status-txt done" wx:if="{{item.status==3}}">已發貨</text>
            </view>
          </view>
          <view class="order-bottom" wx:if="{{item.status==1}}">
            <text>實付:{{item.total_price}}</text>
            <view class="pay" ontap="rePay" data-id="{{item.id}}" data-index="{{index}}">付款</view>
          </view>
        </view>
      </block>
    </view>
  </view>
</view>
<loading hidden="{{loadingHidden}}">
  加載中...
</loading>

注意這一塊回調函數的應用

下拉刷新的時候,要請求接口初始化數據,這一塊用到了回調函數,即初始化了數據也更改了頁面的顯示狀態

/*下拉刷新頁面*/
    onPullDownRefresh: function(){
        var that=this;
        this.data.orderArr=[];  //訂單初始化
        this._getOrders(()=>{
            that.data.isLoadedAll=false;  //是否加載完全
            that.data.pageIndex=1;
            wx.stopPullDownRefresh();
            order.execSetStorageSync(false);  //更新標誌位
        });
    },
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章