微信小程序 电商项目

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/kingrome2017/article/details/84501209

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/home@selected.png",
        "text":"主页"
      },{
        "pagePath":"pages/category/category",
        "iconPath":"imgs/toolbar/category.png",
        "selectedIconPath":"imgs/toolbar/category@selected.png",
        "text":"分类"
      },{
        "pagePath":"pages/cart/cart",
        "iconPath":"imgs/toolbar/cart.png",
        "selectedIconPath":"imgs/toolbar/cart@selected.png",
        "text":"购物车"
      },{
        "pagePath":"pages/my/my",
        "iconPath":"imgs/toolbar/my.png",
        "selectedIconPath":"imgs/toolbar/my@selected.png",
        "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/cart@top.png"></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/arrow@down.png"></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/circle@selected.png"></image>
                            <image wx:else src="../../imgs/icon/circle@noselected.png"></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/all@selected.png"></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/arrow@grey.png"></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/arrow@right.png"></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/user@default.png',
                            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/arrow@right.png"></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);  //更新标志位
        });
    },
展开阅读全文

没有更多推荐了,返回首页