微信小程序之实际项目的组件交互

18648165727

发布于 2019.03.25 16:18 阅读 2895 评论 0

            

  目录:

          1.第一部分

               1.首页的编写和基本逻辑

                    (1)分析主要结构,即组成的wxml文件和wxss文件的编写。

                    (2)c-image自定义组件。

                    (3)编写wx:for遍历商品信息的wxml文件和wxss文件

               2.跳转页面的编写与基本逻辑

                    (1)分析主要结构,即组成的wxml文件和wxss文件的编写。

                    (2)简单的js逻辑并且引出了双重组件的定义。

                    (3)双重组件的嵌套--slot。

                    (4)子组件与父组件的交互  bind:“”。

                    (5)介绍c-quantity子组件的逻辑。

               3.总结

          2.第二部分(http://lindasoft.com/view/article/details?articleId=551)

    在前面举了一个简单的例子,登录界面(http://lindasoft.com/view/article/details?articleId=544)。

   不过那个是一个非常基础的逻辑界面。下面就是微信小程序的升华了。

      接下来的内容是两大部分,请做好准备!!!

 

     首先,先来一张图吧。

   

  1.首先就让我们完成第一幅图的内容吧

  (1)其实第一张图由四部分组成,即搜索框,轮播图,四个图标,和底下的商品样式

  (2)接下来说第二部分。c-image自定义组件的应用

<!--index.wxml-->
<swiper class="banner" autoplay interval="1000" duration="400" indicator-dots="true" circular="true" indicator-color="rgba(255,255,255,.6)" indicator-active-color="#ff4d61">
    <swiper-item wx:for="{{banner}}" wx:key="{{item}}">
        <c-image src="{{item}}" mode="scaleToFill"></c-image>
    </swiper-item>
  </swiper>


<!--index.wxss-->
.banner {
  width: 100vw;
  height: 183px;
}

.swiper-item {
  height: 100%;
}


<!--index.js-->
//获取轮播图渲染列表
  getImgsList() {
    this.setData({
      banner: [
        'http://img0.imgtn.bdimg.com/it/u=1250019442,1455740768&fm=26&gp=0.jpg',
        'http://img0.imgtn.bdimg.com/it/u=1250019442,1455740768&fm=26&gp=0.jpg',
        'http://img0.imgtn.bdimg.com/it/u=1250019442,1455740768&fm=26&gp=0.jpg'
      ]
    })
  },

    但是其实上面就涉及到了自定义组件了,没错,就是c-image,那么这个c-image是在哪里定义的呢。这个自定义组件实在全局的App.json中定义的,就像这样。

 

  这里自定义了四个自定义组件。剩下来的三个接下来我们会用到。

"usingComponents": {
    "c-modal": "./components/c-modal/index",
    "c-dialog": "./components/c-dialog/index",
    "c-quantity": "./components/c-quantity/index",
    "c-image": "./components/c-image/index"
  }

  那么就让我们看看这个所谓自定义组件是怎么写的吧。

 

<!--index.wxml-->
<image
    mode="{{mode}}"
    src="{{src}}"
    lazy-load="{{lazyload}}"
    binderror="error"
    bindload="loaded"
    class="c-image {{visible ? 'visible' : ''}}"
></image>


<!--index.wxss-->
.c-image{width: 100%;height: 100%;opacity: 0;transition: opacity 0.4s;}
.c-image.visible{opacity: 1;}


<!--index.json-->
{
    "component": true
}


<!--index.js-->
Component({
    properties: {
        src: {
            type: String,
            value: ''
        },
        mode: {
            type: String,
            value: 'scaleToFill'
        },
        lazyload: {
            type: Boolean,
            value: false
        }
    },
    data: {
        visible: false
    },
    methods: {
        /*
        * 加载完成
        * */
        loaded() {
            this.setData({
                visible: true
            });
            this.triggerEvent('load');
        },
        /*
        * 加载出错
        * */
        error() {
            this.setData({
                visible: true
            });
            this.triggerEvent('error');
        }
    }
});

/*
* 使用示例:
* <component-image src="{{item}}" mode="aspectFill" bind:error='error' bind:load='load' lazyload='true'></component-image>
* */

  上面的代码就是所谓的一个简单的自定义组件了,其实也可以很简单的看到,这个c-image其实就是把image自定义,这样我们自己就想怎么用就怎么用了,这样做在大型的项目中会节约很多时间。不过在这里体现的不是很明显。接下来会明显的体现出来。

 

  然后说说上面的代码,在wxml文件里面,其实说白了就是对于image的简单封装,在wxss文件里,是对image做了一些样式处理,在json文件中,这里要特别强调一点,所有自定义组件里面一定要加上上面代码里的那个"component":true。

 

  接下来就跳过四个图标部分,直接说第一幅图最关键的地方了。

 

  (3)没错,就是产品列表。

<!--index.wxml-->
<view class="goods-list col-2">
    <navigator class="goods-item" hover-class="none" wx:for="{{10}}" wx:key="index" url="/pages/details/index?id={{index}}" >
      <view class="pic">
        <c-image src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1551870934503&di=583643db807bd42eb605f71c3347af8a&imgtype=0&src=http%3A%2F%2Fwww.emeixian.com%2Fimages%2F201508%2Fgoods_img%2F1889_G_1440874473697.jpg" mode="scaleToFill"></c-image>
      </view>
      <view class="info">
        <view class="title">海南香蕉</view>
        <view class="sub-title">单位:千克</view>
        <view class="price">¥439.00</view>
      </view>
    </navigator>
  </view>

<!--index.wxss-->
.goods-list {
  display: flex;
  flex-wrap: wrap;
  padding: 15rpx 5px;
}

.goods-list.col-2 .goods-item {
  width: 50%;
}

.goods-item {
  display: flex;
  align-items: center;
  margin-bottom: 5rpx;
  padding: 5rpx;
}

.goods-item .pic {
  width: 100px;
  height: 100px;
  flex: none;
}

.goods-item .info {
  flex: 1;
  display: flex;
  flex-direction: column;
  background: #fff;
  height: 100%;
  padding: 5px;
}

.goods-item .info .title {
  font-size: 26rpx;
  padding-top: 5px;
  font-weight: 600;
  flex: 1;
}

.goods-item .info .sub-title {
  font-size: 23rpx;
  color: #969aa3;
  margin-bottom: 5rpx;
}

.goods-item .info .price {
  font-size: 27rpx;
  color: #ef3325;
  padding-bottom: 5px;
}

这里其实就是在navigator上设置了wx:for循环,在这里的跳转页面都是跳转到这幅图的,在这里只是做了一个模板供参考。

在这里布局方法不是唯一的!!!!!!切记!!!!!!

 

2.接下来我们来到了第二幅图的页面,其实就是上面的navigator跳转的页面,在进入第二幅图以后你其实会发现,其实和第一幅图差不多嘛。

  第二幅图其实分了六个部分,都差不多,其实最主要的还是布局的wxss文件里面的内容,在这里补充最重要的一点,尽可能的把每一个样式分开写,这样很利于之后的修改。

 

  (1)接下来就是展示代码了,我重点想说明的只有最后一部分了。

<!--index.wxml-->

<swiper class="goods-banner" autoplay interval="1000" duration="400" indicator-dots="true" circular="true" indicator-color="rgba(255,255,255,.6)" indicator-active-color="#ff4d61">
  <swiper-item wx:for="{{goodsBanner}}" wx:key="{{item}}" class="swiper-item">
    <c-image src="{{item}}" mode="scaleToFill"></c-image>
  </swiper-item>
</swiper>

<view class="goods-info">

  <view class="title">
    <text class="text">海南香蕉</text>
  </view>

  <view class="price">

    <view class="c-price">
      <text>价格</text>
      <text class="val">¥1040.00</text>
    </view>

    <view class="o-price">
      <text>原价¥1351.00</text>
    </view>

  </view>

</view>


<view class="info">
  <view class="info-item txt-left">
    <text>运费:0</text>
  </view>
  <view class="info-item txt-center">
    <text>销量:1200</text>
  </view>
  <view class="info-item txt-right">
    <text>库存:1200</text>
  </view>
</view>


<view class="info" bindtap="showBuyDialog">

  <view class="info-item">
    <text>规格</text>
  </view>
  <view class="info-item txt-right">
    <text class="line">选择规格></text>
  </view>

</view>


<view class="info" bindtap="toComment">

  <view class="info-item">
    <text>用户评价</text>
  </view>
  <view class="info-item txt-right">
    <text class="line">查看评价></text>
  </view>
  
</view>


<view class="introduce-title">
  <text class="line">图文详情</text>
</view>
<view class="introduce-content">
<image mode="aspectFit" src="//img12.360buyimg.com/n1/s450x450_jfs/t1/27491/2/4431/289258/5c321c30E280237c3/86ed242b2360a2cd.jpg"></image>
<text>菜篮子产递</text>
</view>



<!-- sku --><!--双重自定义组件-->
<c-dialog show="{{showDialog}}">
<buy-inner bind:hideBuyDialog="hideBuyDialog"/>
</c-dialog>


<!--index.wxss-->
.goods-banner {
  width: 100vw;
  height: 370rpx;
}

.goods-info {
  margin-bottom: 20rpx;
  padding: 20rpx;
  background-color: #fff;
}

.goods-info .title {
  margin-bottom: 10rpx;
}
.goods-info .title .text {
  font-size: 32rpx;
  color: #333;
  font-weight: 600;
}

.goods-info .price {
  font-size: 24rpx;
  display: flex;
  
  /*交叉轴终点对齐*/
}

.goods-info .price .c-price {
  color: #ef3325;
  margin-right: 20rpx;
}

.goods-info .price .c-price .val {
  font-size: 32rpx;
}

.goods-info .price .o-price {
  color: #bbb;
  font-style:  oblique;/*浏览器会显示一个倾斜的字体样式*/
  text-decoration:line-through;/*定义穿过文本的一条线*/
}

.info{
  display: flex;
  justify-content: space-between;
  /*两端对齐,项目之间都相隔*/
  padding: 20rpx;
  border-top: 3rpx solid #eee;
}
.info .info-item{
  width: 33.3%;
}
.info .txt-left{
  text-align: left;
}
.info .txt-center{
  text-align: center;
}
.info .txt-right{
  text-align: right;
}
.line{
  color:#ef3325;
}


.introduce-title{
  padding: 20rpx;
  border-top: 8rpx solid #eee;
}
.introduce-content{
  display: flex;
  align-items: center;
  flex-direction: column;
  /*主轴为垂直方向*/
  padding: 20rpx;
  border-top: 3rpx solid #eee;
  font-size: 24rpx;
}
.introduce-content image{
  width: 100%;
  margin-bottom: 15rpx;
}


<!--index.json-->
{
    "navigationBarTitleText": "商品详情",
    "usingComponents": {
        "buy-inner": "_components/buy-inner/index"
    }
}


<!--index.js-->
const app = getApp();
Page({
  data: {
    detailInfo:{},
    goodsBanner: [
      '//img12.360buyimg.com/n1/s450x450_jfs/t1/27491/2/4431/289258/5c321c30E280237c3/86ed242b2360a2cd.jpg',
      '//img12.360buyimg.com/n1/s450x450_jfs/t1/18848/17/4571/212369/5c32ce88E1ddec98d/67af5d6fb1577cd3.jpg',
      '//img12.360buyimg.com/n1/s450x450_jfs/t1/7211/16/8954/485449/5c1088ecE9884e066/542088e742e6169c.jpg'
    ],
    showDialog:false
  },
  onLoad(options) {
    console.log(options.id)
  },

  //获取货物详情信息
  getGoodDetail(){
    let info = {
      name:"",
    }
    this.setData({
      detailInfo:info
    })
  },

  //获取详情介绍
  getIntroduce(){
    
  },

  //点击规格,弹窗显示
  showBuyDialog(){
    this.setData({
      showDialog:true
    })
  },

  hideBuyDialog() {
    this.setData({
      showDialog: false
    })
  },

  //点击用户评价,前往评价页面
  toComment(){
    wx.navigateTo({
      url: 'comment/index?id=' + this.data.detailInfo.id,
    })
  }

});

  在wxml文件里的最后一部分,就是最重要的组件的交互,其实是在自定义组件里面自定义了组件,我叫他双重自定义组件。那么在最里面定义的那个自定义组件没有在app.json中定义,那么你就只能在index.json中定义了。

 

(2).接下了该说上面说到的那个双重自定义组件了。

   

    这个就是双重组件的图片。

   既然是双重自定义组件,那么就先从最外面的那个说起吧,c-dialog

<!--index.wxml-->
<view class="dialog {{className}} {{ show ? 'dialog_show' : '' }}">

    <!--遮罩-->
    <view class="dialog__mask" catchtap="hide"/>

    <!--container-->
    <view class="dialog__container">
        <!--右上角关闭按钮-->
        <image class="close" catchtap="hide" src="icons/icon_close.png"></image>
        <slot></slot>
    </view>
</view>



<!--index.wxss-->
.dialog__mask{position:fixed;top:0;right:0;bottom:0;left:0;z-index:10;visibility:hidden;background:rgba(0,0,0,.65);opacity:0;transition:all .4s ease}
.dialog__container{position:fixed;z-index:11;visibility:hidden;background:#fff;opacity:0;transition:all .4s ease;overflow:hidden;}
.dialog__container .close{position:absolute;top:12px;right:10px;width:20px;height:20px;}

.dialog .dialog__container{right:0;bottom:0;left:0;height:inherit; transform:translateY(50%);flex-direction:column}
.dialog.dialog_show .dialog__container{transform:translateY(0)}
.dialog.dialog_show .dialog__container{visibility:visible;opacity:1;min-height:36vh;}
.dialog.dialog_show .dialog__mask{visibility:visible;opacity:1}


<!--index.json-->
{
    "component": true
}



<!--index.js-->
Component({
    options: {
        multipleSlots: false // 在组件定义时的选项中启用多slot支持
    },
    properties: {
        show: {
            type: Boolean,
            value: false
        }
    },
    data: {},
    methods: {
        // 打开弹窗
        show() {
            this.setData({
                show: true
            });
        },
        // 关闭弹窗
        hide() {
            this.setData({
                show: false
            });
        },

    }
});

  在这里还要说一下前面的东西,有一句show="{{showDialog}}" 这个为true才是上面双重组件显示的基础。

 

  (3)接下来说c-dialog。其实吧,基本上所有的自定义组件都是一样的,就像c-image差不多吧。只不过这个组件里面特殊了一些,在c-dialog里面吧存在slot组件吧。这个slot其实就是前面的双重自定义组件里面的子组件。

 

  在看index.json里面,若是app.json里面没有定义的自定义组件那么就在该页面的index.json里面自定义就好,顺便在js文件里面multipleSlots: false // 在组件定义时的选项中启用多slot支持,这样这个最外层就解决了。

 

  接下里看buy-inner,其实这个组件也是其他的自定义组件差不多,只不过就是加了一点点其他的东西。

<!--index.wxml-->
<view class="sku_container">
  <!--header-->
  <view class="sku_hd">
    <image class="sku-pic" src="//m.360buyimg.com/mobilecms/s750x750_jfs/t1/8332/4/12559/107539/5c3644b5E0a2ab9eb/507437b95b0abbad.jpg!q80.dpg"></image>
    <view class="sku-desc">
      <view class="title">海南香蕉</view>
      <view class="stock">库存:900</view>
      <view class="price">价格¥690</view>
      <view class="o-price">原价¥690</view>
    </view>
  </view>

  <!--body-->
  <view class="sku_bd">

    <view class="sku-quantity">
      <view class="title">购买数量</view>
      <c-quantity bind:change="onQuantityChange"></c-quantity>
    </view>
  </view>

  <!--footer-->
  <view class="sku_ft">
    <view class="btn-add-shop" catchtap="addToShopCart">加入购物车</view>
    <view class="btn-buy-now" catchtap="toBuyNow">立即购买</view>
  </view>
  
</view>


<!--index.wxss-->
.sku_container {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: 20rpx;
}

.sku_hd {
  flex: none;
  padding: 20rpx 0;
  border-bottom: 1rpx solid #f0f0f0;
  text-align: center;
  font-size: 34rpx;
  margin-bottom: 20rpx;
}

.sku_hd .title {
  color: #333;
}

.sku_bd {
  flex: 1;
}

.sku_ft {
  flex: none;
  display: flex;
  justify-content: center;
  margin-top: 10rpx;
}

.sku_ft .btn-add-shop {
  flex: 1;
  border-radius: 10rpx;
  padding: 20rpx 0;
  font-size: 30rpx;
  color: #fff;
  background: #FFCC33;
  text-align: center;
}

.sku_ft .btn-buy-now {
  flex: 1;
  border-radius: 10rpx;
  padding: 20rpx 0;
  font-size: 28rpx;
  color: #fff;
  background: #FF6600;
  text-align: center;
}


.sku_hd {
  display: flex;
}

.sku-pic {
  width: 150rpx;
  height: 150rpx;
  flex: none;
  margin-right: 20rpx;
}

.sku-desc {
  flex: 1;
  text-align: left;
  font-size: 32rpx;
  margin-right: 60rpx;
}

.sku-desc .title {
  color: #333;
  font-weight: 600;
  
}
.sku-desc .stock{
  color: #aaa;
  font-size: 25rpx;
}
.sku-desc .o-price {
  color: #bbb;
  font-style:  oblique;
  text-decoration:line-through;
  font-size: 27rpx;
}
.sku-desc .price {
  color: #ff4d61;
  font-size: 27rpx;
}


.sku-quantity {
  margin-bottom: 20rpx;
  display: flex;
  justify-content: space-between;
}

.sku-quantity .title {
  font-size: 32rpx;
  color: #333;
  font-weight: 600;
}



<!--index.json-->
{
  "component": true
}


<!--index.js-->
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    info: {
      type: Object,
      default: {}
    },
    quantity: {
      type: Number,
      value: 1,
    }
  },

  /**
   * 组件的初始数据
   */
  data: {

  },

  /**
   * 组件的方法列表
   */
  methods: {
    onQuantityChange(e) {
      this.data.quantity = e.detail;
      console.log(this.data.quantity);
    },

    //添加到购物车
    addToShopCart(){
      console.log(this.data.quantity);
      //请求添加购物车界面
    },

    //立即购买
    toBuyNow() {
      this.triggerEvent('hideBuyDialog');
      wx.navigateTo({
        url: '../../pages/shopping-cart/buy-now/index',
      })
    },
  }
})

   所有的自定义组件都差不多,那么wxss和json就不说了。先说wxml文件吧。

   有一个前面没有出现过的东西。<c-quantity bind:change="onQuantityChange"></c-quantity>

 

   (4).这个其实就是自定义了一个组件,其实也没有什么,但是它自定义组件里面是这样写的。bind:change=“onQuantityChange”在组件之间如果这样写的话就说了一个最最重要的问题,这样写视为了组件之间的交互,即如两个组件,一个父组件一个子组件。当子组件要给父组件传数据的时候,那么在父组件里面的引用子组件就要这样写。

   

   然后在看js文件吧。出现了一个onQuantityChange(e) {this.data.quantity = e.detail;  console.log(this.data.quantity);  }

   

   这个函数其实就是为了接受传过来的数据,在这个函数里面,这个e.detail就是传过来的数据。

   这就是简单组件与组件之间的数据交互。

 

   (5)那么我们最后来看c-quantity。

   这个就是上面说到的子组件了,代码如下:

   

<!--index.wxml-->
<view class="c-quantity">
    <view class="btn decrement {{quantity<2 ? 'disabled' : ''}}"
          catchtap="quantity_decrement"></view>
    <view class="num">{{quantity}}</view>
    <view class="btn increment {{quantityLimit && quantity >= quantityLimit ? 'disabled' :''}}"
          catchtap="quantity_increment"></view>
</view>



<!--index.wxss-->
.c-quantity {
  overflow: hidden;
  display: inline-flex;
  border: 1px solid #dcdcdc;
  height: 60rpx;
  line-height: 60rpx;
  text-align: center;
}

.c-quantity .btn {
  width: 60rpx;
  height: 100%;
}

.c-quantity .btn.disabled {
  color: #dcdcdc;
}

.c-quantity .decrement:before {
  content: '-';
  font-size: 40rpx;
  line-height: 1;
}

.c-quantity .increment:before {
  content: '+';
  font-size: 40rpx;
  line-height: 1;
}

.c-quantity .num {
  min-width: 64rpx;
  height: 100%;
  border-left: 2rpx solid #dcdcdc;
  border-right: 2rpx solid #dcdcdc;
}



<!--index.json-->
{
    "component": true
}




<!--index.js-->
Component({
    properties: {
        quantity: {
            type: Number,
            value: 1,
        },
        quantityLimit: {
            type: Number,
            value: 10000,
        }
    },
    data: {},
    methods: {
        /*
       * 数量减少
       * */
        quantity_decrement() {
            let _count = this.data.quantity - 1, _min = 1;
            if (_count < _min) return false;
            this.setData({
                quantity: _count
            });
            this.triggerEvent('change', _count);
        },
        /*
        * 数量增加
        * */
        quantity_increment() {
            let _count = this.data.quantity + 1, _max = this.data.quantityLimit;
            if (_count > _max) return false;
            this.setData({
                quantity: _count
            });
            this.triggerEvent('change', _count);
        },
    }
});

   其实这个自定义组件还是和其他的一样,那么就不说wxss和json了,先说wxml。

   其实就是设置了两个可以点击的view和一个显示数值的view而已。两个可以点击的view,然后设置在逻辑里面设置class属性。

 

   下面是js文件。

     在这个js中我主要想说的就是this.traggerEvent();这个语句。

   你可以网上看,在那两个函数中是这样写的this.triggerEvent('change', _count);,这个其实就是父组件就是子组件之间子组件给父组件传输的语句了,在这里简单的说一下逻辑和traggerEvent。

  

   this.traggerEvent()这个东西上面传了两个参数,一个是change、一个是_count,第一个参数是你要传递的父组件的传递的那个,怎么讲,之前父组件不是这样写的吗?  <c-quantity bind:change="onQuantityChange"></c-quantity> 因为父组件是bind:change这样写的,所以第一个参数就要写change,那么第二个参数是什么呢,其实我上面已经暗示了,就是:

 

   onQuantityChange(e) {this.data.quantity = e.detail;  console.log(this.data.quantity);  },在这个函数里面有一个e,并且函数里面也有一个e.detail,那么这个_count其实就是e,那么这就是简单组件之间的交互逻辑了。

     

   不过在这里主要没有演示传递过来的数据,只是说了一下,在下一个例子说了传过来的数据该怎么用。

    那么,其实这就是简单的组件与组件之间的联系与交互。

    

 

 

 

   总结:

   1.在这里可能看到其实在这个例子中用组件更麻烦了,其实用组件是为了更加方便的管理代码,就像我们的c-quantity,就会在下一个案例中反复用到。所以只有重复用到的地方考虑组件,只用一次最好不要用组件。

   2.组件之间的数据交互也很重要。

   3.在写wxss和wxml代码的时候,最重要的是一条wxss属性最好单独的写在一个view中,这样写的时候可能会很麻烦,但是非常容易再后面管理。