微信小程序学习

 

将自己图片上传为网络图片:https://images.ac.cn

阿里巴巴矢量图标库:https://www.iconfont.cn  1.选择图标 2.加入下载 3.创建项目 4.显示在线链接,复制链接,复制链接内的内容 5.将链接内容保存到项目的style文件夹下的.wxss文件中 6.在app.wxss中引入即可使用

 

细节:

在标签的属性中使用: <view data-num="{{num}}">自定义属性</view>

使用bool类型充当属性 checked:(checkbox是以前的复选框标签)<view><checkbox checked="{{}}"></checkbox></view>   (字符串和花括号之间一定不能存在空格 否则会导致识别失败)

运算 =>表达式

         1.可以在花括号中 加入表达式 -- “语句”

         2.表达式  是指一些简单 运算 数字运算 字符串 拼接 逻辑运算

                        数字的加减、 字符串拼接、三元表达式

         3语句 复杂的代码段 if-else switch do-while for

列表循环

    1.wx:for="{{数组或者对象}}" wx:for-item="循环项的名称"  wx:for-index="循环项的索引"

    2.wx:key="唯一的值" 用来提高列表渲染的性能

           wx:key 绑定一个普通的字符串的时候 那么这个字符串名称 肯定是 循环数组 中的 对象的 唯一属性

          wx:key="*this" 就表示 你的数组 是一个普通的数组 *this 表示是 循环项

    3.当出现 数组的嵌套循环的时候 尤其要注意 以下绑定的名称 不要重名

           wx:for-item="item" wx:for-index="index"

    4.默认情况下 我们不写

            wx:for-item="item" wx:for-index="index"

         小程序也会把 循环项的名称 和 索引的名称 item 和 index         只有一层循环的话 (wx:for-item="item" wx:for-index="index")  可以省略

 

对象循环

    1.wx:for="{{对象}}" wx:for-item="对象的值"  wx:for-index="对象的属性"

block

    占位符的标签        写代码的时候,可以看到这标签存在           页面渲染,小程序会把它移除掉

条件渲染

    wx:if="{{true/false}}"

   hidden   1.在标签上直接加入属性hidden       2.hidden="{{true}}"

   什么场景用哪个:

               1.当标签表示频繁切换显示 优先使用 wx:if            直接把标签从页面结构给移除掉

               2.当标签频繁的切换显示的时候 优先使用 hidden       通过添加样式的方式来切换显示

 

需要给input标签绑定 input事件  :绑定关键字 bindinput

如何获取输入框的值:通过事件源对象来获取 e.detail.value

把输入框的值 赋值到data当中 : 不能直接1.this.data.num=e.detail.value   2.this.num=e.detail.value

                                                      正确写法 this.setData({num: e.detail.value})

需要加入一个点击事件 1.bindtap  2.无法在小程序当中的 事件中 直接传参  3.通过自定义属性的方式来传递参数  4.事件源中获取 自定义属性

                                 <button bindtap="handletap" data-operation="{{1}}">+<button>

 

尺寸单位

        rpx,规定屏幕宽为750rpx,ipone6屏幕为375px(750rpx=375px)

1.小程序中不需要主动引入样式文件

2.需要把页面中某些原生的单位  由px改为rpx  设计稿750px:750px=750rpx  1px=1rpx

                                                                          设把屏幕宽度改为 375px 375px=750rpx  1rpx=0.5px

3.存在一个设计稿 宽度414 或者 未知page  设计稿 page 存在一个元素 宽度 100px   拿以上的需求 实现 不同宽度页面适配  

                           page px=750rpx   1px=750rpx/page  100px=750rpx*100/page

4.利用一个属性 calc属性 css和wxss 都支持一个属性(要求:750和rpx中间不能有空格;运算符的两边也不要留空格) 

                           width:calc(750rpx*100/375);

用import导入,必须使用相对路径

 

常见组件

view

text

        1.文本标签    2.只能嵌套text    3.长按文字可以复制selectable(只有该标签有此功能)   4.可以对空格 回车 进行编码decode

image

        1.默认宽320px、高240px    2.支持懒加载

        src:图片路径

       mode决定图片内容如何 和 图片标签 宽高做适配

                   scaleToFill默认值 不保持丛横比缩放图片,使图片的宽高完全拉伸至填满 image元素

       lazy-load懒加载:会自己判读 当图片出现在视口上下 三屏的高度之内的时候 自己开始加载图片

swiper

       轮播图外层容器 swiper         每一个轮播项 swiper-item

       swiper标签 存在默认样式 :width 100%  height 150px  image存在默认宽度和高度320*240         swiper高度无法实现由内容撑开

      先找出来 原图的宽度和高度 等比例 给swiper 定宽度和高度

                  eg:原图的宽度和高度 1125 * 352 px         swiper 宽度/swiper高度 = 原图宽度 / 原图高度               swiper高度 = swiper宽度 * 原图高度 / 原图宽度

                         height:100vw * 352 / 1125

      autoplay 自动轮播

     interval 修改轮播时间

      circular 衔接轮播

      indicator-dots 显示 指示器 分页器 索引器

      indicator-color 指示器的未选择颜色

      indicator-active-color 选中的时候指示器颜色

navigator导航标签

button

   share:转发当前小程序到微信朋友中,不能把小程序分享到朋友圈

   getPhoneNumber 获取当前用户的手机号码信息 结合一个事件使用  不是企业的小程序账户 没有权限获取用户的手机号码

             1.绑定一个事件 bindgetphonenumber   2.在事件回调函数中 通过参数来获取信息  3.获取到的信息 已经加密  (需要用户自己搭建小程序的后台服务器,在后台服务器中进行解析 手机号码,返回到小程序中 就可以看到信息了)

radio 单选框

         radio标签必须和父元素radio-group来使用     value选中的单选框的值    需要给radio-group绑定change事件   需要在页面中显示选中的值

checkbox 复选框

 

自定义组件

         回调函数:

                       1.页面.js 文件中 存放事件回调函数的时候 存放在data同层级下

                       2.组件.js 文件中 存放事件回调函数的时候 必须要存在在 methods中

        循环数组: [].forEach 遍历数组 遍历数组的时候 修改了v  也会导致源数组被修改

       父向子组件传值:通过标签属性的方式来传递

父组件wxml
<Tabs aaa="123"></Tabs>

子组件
properties: {
    // 要接收的数据的名称
    aaa:{
      type:String,
      value:""
    }
  }

子组件的wxml
<view>
  {{aaa}}
</view>

       子向父传递数据 通过事件的方式传递

 

在子组件的js文件的触发自定义事件中改为:this.triggerEvent("itemChange",{index})


在父组件的wxml文件中加入绑定事件:<Tabs tabs="{{tabs}}" binditemChange="handleItemChange"></Tabs>



在父组件的js文件的触发事件中加入:
handleItemChange(e){
    //  2 获取索引
    const {index}=e.currentTarget.dataset;
    let {tabs}=this.data;
      // let tabs=this.data;
      // 4 循环数组
      // [].forEach 遍历数组 遍历数组的时候 修改了 v ,也会导致源数组被修改
      tabs.forEach((v,i)=>i===index?v.isActive=true:v.isActive=false);

      this.setData({
        tabs
      })
  }

     slot标签其实就是一个占位符,插槽;等到父组件调用子组件的时候,再传递标签过来,最终这些被传递的标签会替换slot插槽位置

 

应用生命周期:

onLaunch:应用第一次启动的时候 获取用户的个人信息

 

onShow:对应用的数据或者页面效果 重置(不同于onLoad,无法在形参上接收options参数)

            如何获取:

                               1. 获取小程序的页面栈-数组 长度最大是10页面  let pages = getCurrentPages();

                               2.数组中 索引最大的页面就是当前页面   let currentPage = pages[pages.length-1]

 

onHide:暂停或者清除定时器

onError:在应用发生代码报错的时候,收集用户的错误信息,通过异步请求 将错误信息发生后台去

onPageNotFound:应用第一次启动的时候,如果找不到第一个入口页面才会触发(如果页面不存在了 通过js的方式来重新跳转页面 重新跳到第二个页面)

                             wx.navigateTo({url:''})

页面生命周期

 

 

小程序的第三方框架:

腾讯wepy   语法类型vue

美团mpvue    语法类型vue

京东taro   类型react

滴滴chameleon

uni-app    语法类型vue

 

 

项目:

1.在链接外部url时,遇到域名问题(request问题)

一定勾选这个选项,不然访问不了url;或者在request中添加域名

2.回调地狱(顺便解决定义公共url)

 // 定义公共的url
  const baseUrl="https://api.zbztb.cn/api/public/v1";
  return new Promise((resolve,reject)=>{
    wx.request({
     ...params,
     header:header,
     url:baseUrl+params.url,
     success:(result)=>{
       resolve(result.data.message);
     },
     fail:(err)=>{
       reject(err);
     },
     complete:()=>{
      ajaxTimes--;
      if(ajaxTimes===0){
        //  关闭正在等待的图标
        wx.hideLoading();
      }
     }
    });
  })

es7的async语法(兼容问题大):是解决回调地狱的最终方案

1. 在⼩程序的开发⼯具中,勾选 es6转es5语法

2. 下载 facebook的regenerator库中的 regenerator/packages/regenerator-runtime/runtime.js

3. 在⼩程序⽬录下新建⽂件夹 lib/runtime/runtime.js ,将代码拷⻉进去

4. 在每⼀个需要使⽤async语法的⻚⾯js⽂件中,都引⼊(不能全局引⼊)

 import regeneratorRuntime from '../../lib/runtime/runtime';
// 1 使用es7的async await来发送请求
    const res = await request({ url: "/categories" });
    // this.Cates = res.data.message;
    this.Cates = res;
    // 把接口的数据存入到本地存储中
    wx.setStorageSync("cates", { time: Date.now(), data: this.Cates });
    // 构造左侧的大菜单数据
    let leftMenuList = this.Cates.map(v => v.cat_name);
    // 构造右侧的商品数据
    let rightContent = this.Cates[0].children;
    this.setData({
      leftMenuList,
      rightContent
    })
  },

3.轮播图的设置

主要是注意图片、轮播图都有其默认值

.js文件:
//Page Object
Page({
  data: {
    // 轮播图数组
    swiperList: [],
    // 导航数组
    cateList: []
  },
  //页面开始加载 就会触发
  onLoad: function (options) {
    //1.发送异步请求获取轮播图数据
    // wx.request({
    //   url: 'https://api.it120.cc/kotoba/banner/list',
    //   success: (result) => {
    //     this.setData({
    //       swiperList: result.data.data
    //     })
    //   }
    // });
    this.getSwiperList();
    this.getCateList();
  },

  // 获取轮播图数据
  getSwiperList() {
    request({
      url: "/banner/list"
    }).then(result => {
      this.setData({
        swiperList: result.data.data
      })
    })
  },



.wxml文件:
<view class="xyr_index">
  <!-- 搜索框 开始 -->
  <SearchInput></SearchInput>
  <!-- 搜索框 结束 -->
  <!-- 轮播图 开始 -->
  <view class="index_swiper">
  <!-- 
      1 swiper标签存在默认的宽度和高度
        100% * 150px 
      2 image标签也存在默认的宽度和高度
        320px * 240px 
      3 设计图片和轮播图
        1 先看一下原图的宽高  750 * 340 
        2 让图片的高度自适应 宽度 等于100%
        3 让swiper标签的高度 变成和图片的高一样即可  
      4 图片标签
        mode属性 渲染模式
          widthFix  让图片的标签宽高 和 图片标签的内容的宽高都等比例的发生变化 
     -->
    <swiper autoplay indicator-dots circular>
      <swiper-item
      wx:for="{{swiperList}}"
      wx:key="id"
      >
        <navigator>
          <image mode="widthFix" src="{{item.picUrl}}"></image>
        </navigator>
      </swiper-item>
    </swiper>
  </view>
  <!-- 轮播图 结束 -->




.index_swiper swiper {
  width: 750rpx;
  height: 340rpx;
}
.index_swiper swiper image {
  width: 100%;
}

轮播图放大预览,点击轮播图 预览大图
  1 给轮播图绑定点击事件
  2 调用小程序的api  previewImage 

// 商品对象
  GoodsInfo: {},

//获取到商品对象内容
  const goodsObj = await request({ url: "/goods/detail", data: { goods_id } });
    this.GoodsInfo = goodsObj;

// 点击轮播图 放大预览
  handlePrevewImage(e) {
    // 1 先构造要预览的图片数组 
    const urls = this.GoodsInfo.pics.map(v => v.pics_mid);
    // 2 接收传递过来的图片url
    const current = e.currentTarget.dataset.url;
    wx.previewImage({
      current,
      urls
    });

  },




<swiper-item
    wx:for="{{goodsObj.pics}}"
    wx:key="pics_id"
    bindtap="handlePrevewImage"
    data-url="{{item.pics_mid}}"
    >
    <image mode="widthFix" src="{{item.pics_mid}}" ></image>
    </swiper-item>

 

 

 

4.图片之间的处理

效果图:

.wxss文件:

.index_floor .floor_group .floor_title {
  padding: 10rpx 0;
}
.index_floor .floor_group .floor_title image {
  width: 100%;
}
.index_floor .floor_group .floor_list {
  overflow: hidden;
}
.index_floor .floor_group .floor_list navigator {
  float: left;
  width: 33.33%;
  /* 后四个超链接 */
  /* 2 3 两个超链接 */
}
.index_floor .floor_group .floor_list navigator:nth-last-child(-n+4) {
  /* 原图的宽高 232 *386 */
  height: 27.72711207vw;
  border-left: 10rpx solid #fff;
}
.index_floor .floor_group .floor_list navigator:nth-child(2),
.index_floor .floor_group .floor_list navigator:nth-child(3) {
  border-bottom: 10rpx solid #fff;
}
.index_floor .floor_group .floor_list navigator image {
  width: 100%;
  height: 100%;
}


.wxml文件:

<!-- 楼层 开始 -->
  <view class="index_floor">
    <view class="floor_group"
    wx:for="{{floorList}}"
    wx:for-item="item1"
    wx:for-index="index1"
    wx:key="floor_title"
    >
      <!-- 标题 -->
      <view class="floor_title">
        <image mode="widthFix" src="{{item1.floor_title.image_src}}"></image>
      </view>
      <!-- 内容 -->
      <view class="floor_list">
        <navigator 
        wx:for="{{item1.product_list}}"
        wx:for-item="item2"
        wx:for-index="index2"
        wx:key="name"
        url="{{item2.navigator_url}}"
        >
        <image mode="{{index2===0?'widthFix':'scaleToFill'}}" src="{{item2.image_src}}"></image>
      </navigator>
      </view>
    </view>
  </view>
  <!-- 楼层 结束 -->





5.获取接口的数据(map)

orders为对象,res.orders.map(v=>({...v,create_time_cn:(new Date(v.create_time*1000).toLocaleString()})) 把orders对象展开

create_time:属性名;create_time_cn:表示处理后的时间

 

6.less和wxss

 // less中使用calc的时候要注意

        height: ~'calc(100vh-90rpx)';

水平居中,垂直居中:justify-content: center;    align-items: center;

flex-direction:column;主轴方向变成上下

使用主题颜色:color: var(--themeColor);

view{$}*num:生成num个view

 

        display: -webkit-box;
        overflow: hidden;
        -webkit-box-orient: vertical;
        -webkit-line-clamp:2;

第几行显示。。。

 

文本超出后隐藏:

       overflow: hidden;

        white-space: nowrap;

        text-overflow: ellipsis;

 

标签字符串直接配合标签显示需要配合富文本

<rich-text nodes="{{标签字符串}}"></rich-text>

 

当写好wxml时可以直接使用vs code中的插件生成相应less(全选之后,ctrl+p,使用插件》 Generate Css tree即可)

 

个人中心中背景图片的模糊处理(高斯模糊filter):filter:blur(10rpx);数值越大越模糊

 

实现内容换行:加上伸缩盒子,换行

      display: flex;

      flex-wrap: wrap;

从右边往左边对其:

    display: flex;

    justify-content: flex-end;

加边框圆角:border-radius

 

7.左侧菜单点击事件

  handleItemTap(e) {
    /* 
    1 获取被点击的标题身上的索引
    2 给data中的currentIndex赋值就可以了
    3 根据不同的索引来渲染右侧的商品内容
     */
    const { index } = e.currentTarget.dataset;

    let rightContent = this.Cates[index].children;
    this.setData({
      currentIndex: index,
      rightContent,
      // 重新设置 右侧内容的scroll-view标签的距离顶部的距离
      scrollTop: 0
    })

  }

8.缓存技术

    1 先判断一下本地存储中有没有旧的数据
      {time:Date.now(),data:[...]}
    2 没有旧数据 直接发送新请求 
    3 有旧的数据 同时 旧的数据也没有过期 就使用 本地存储中的旧数据即可

    web中的本地存储和 小程序中的本地存储的区别
      1 写代码的方式不一样了 
        web: localStorage.setItem("key","value") localStorage.getItem("key")
        小程序中: wx.setStorageSync("key", "value"); wx.getStorageSync("key");
      2:存的时候 有没有做类型转换
        web: 不管存入的是什么类型的数据,最终都会先调用以下 toString(),把数据变成了字符串 再存入进去
         小程序: 不存在 类型转换的这个操作 存什么类似的数据进去,获取的时候就是什么类型

  //  1 获取本地存储中的数据  (小程序中也是存在本地存储 技术)
    const Cates = wx.getStorageSync("cates");
    // 2 判断
    if (!Cates) {
      // 不存在  发送请求获取数据
      this.getCates();
    } else {
      // 有旧的数据 定义过期时间  10s 改成 5分钟
      if (Date.now() - Cates.time > 1000 * 10) {
        // 重新发送请求
        this.getCates();
      } else {
        // 可以使用旧的数据
        this.Cates = Cates.data;
        let leftMenuList = this.Cates.map(v => v.cat_name);
        let rightContent = this.Cates[0].children;
        this.setData({
          leftMenuList,
          rightContent
        })
      }
    }

  }

9.左侧点击菜单选项,右侧该选项标题置顶

            设置scroll-top:在获取右边内容后将其设置为0即可

handleItemTap(e) {
    /* 
    1 获取被点击的标题身上的索引
    2 给data中的currentIndex赋值就可以了
    3 根据不同的索引来渲染右侧的商品内容
     */
    const { index } = e.currentTarget.dataset;

    let rightContent = this.Cates[index].children;
    this.setData({
      currentIndex: index,
      rightContent,
      // 重新设置 右侧内容的scroll-view标签的距离顶部的距离
      scrollTop: 0
    })

10.获取分类id并在打开这个页面时就存在

  url="/pages/goods_detail/index?goods_id={{item.goods_id}}"

 在onLoad: function (options) {option}

修改编译,一定记得写启动参数cid=5

11.完整自定义组件

子组件Tabs

.js文件:

// components/Tabs/Tabs.js
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    tabs:{
      type:Array,
      value:[]
    }
  },

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

  },

  /**
   * 组件的方法列表
   */
  methods: {
    // 点击事件
    handleItemTap(e){
      // 1 获取点击的索引
      const {index}=e.currentTarget.dataset;
      // 2 触发 父组件中的事件 自定义
      this.triggerEvent("tabsItemChange",{index});
    }
  }
})


.wxml文件:
<view class="tabs">
  <view class="tabs_title">
    <view
    wx:for="{{tabs}}"
    wx:key="id"
    class="title_item  {{item.isActive?'active':''}} "
    bindtap="handleItemTap"
    data-index="{{index}}"
    >

    {{item.value}}
  </view>
  </view>
  <view class="tabs_content">
    <slot></slot>
  </view>
</view>


.json文件:
{
  "component": true,
  "usingComponents": {}
}


.wxss文件:
.tabs{}
.tabs_title{
  display: flex;
 
}
.title_item{
  display: flex;
  justify-content: center;
  align-items: center;
  flex: 1;
  padding: 15rpx 0;
}
.active{
  color:var(--themeColor);
  border-bottom: 5rpx solid currentColor;
}
.tabs_content{}

父组件:

.js文件:
Page({
  data: {
    tabs: [
      {
        id: 0,
        value: "综合",
        isActive: true
      },
      {
        id: 1,
        value: "销量",
        isActive: false
      },
      {
        id: 2,
        value: "价格",
        isActive: false
      }
    ],
    goodsList:[]
  },
// 标题点击事件 从子组件传递过来
  handleTabsItemChange(e){
    // 1 获取被点击的标题索引
    const {index}=e.detail;
    // 2 修改源数组
    let {tabs}=this.data;
    tabs.forEach((v,i)=>i===index?v.isActive=true:v.isActive=false);
    // 3 赋值到data中
    this.setData({
      tabs
    })
  },


.json文件:
{
  "usingComponents": {
    "SearchInput":"../../components/SearchInput/SearchInput",
    "Tabs":"../../components/Tabs/Tabs"

  },
  "navigationBarTitleText": "商品列表",
  "enablePullDownRefresh":true,
  "backgroundTextStyle":"dark"
}


.wxml文件
<!-- 监听自定义事件 -->
<Tabs tabs="{{tabs}}" bindtabsItemChange="handleTabsItemChange" >
  
  <block wx:if="{{tabs[0].isActive}}">
    <view class="first_tab">
        <navigator class="goods_item"
        wx:for="{{goodsList}}"
        wx:key="goods_id"
        url="/pages/goods_detail/index?goods_id={{item.goods_id}}"
        >
            <!-- 左侧 图片容器 -->
            <view class="goods_img_wrap">
              <image mode="widthFix" src="{{item.goods_small_logo?item.goods_small_logo:'https://ww1.sinaimg.cn/large/007rAy9hgy1g24by9t530j30i20i2glm.jpg'}}"></image>
            </view>
            <!-- 右侧 商品容器 -->
            <view class="goods_info_wrap">
              <view class="goods_name">{{item.goods_name}}</view>
              <view class="goods_price">¥{{item.goods_price}}</view>
            </view>
          </navigator>
    </view>
  </block>
  <block wx:elif="{{tabs[1].isActive}}">1</block>
  <block wx:elif="{{tabs[2].isActive}}">2</block>

</Tabs>


.less文件:
/* pages/goods_list/index.wxss */
.first_tab{
  .goods_item{
    display: flex;
    border-bottom: 1px solid #ccc;
    .goods_img_wrap{
      flex: 2;
      display: flex;
      justify-content: center;
      align-items: center;
      image{
        width: 70%;
      }
    }
    .goods_info_wrap{
      flex: 3;
      display: flex;
      flex-direction: column;
      justify-content: space-around;
      .goods_name{
        display: -webkit-box;
        overflow: hidden;
        -webkit-box-orient: vertical;
        -webkit-line-clamp:2;
      }
      .goods_price{
        color: var(--themeColor);
        font-size: 32rpx;
      }
    }
  }
}

 

12.页面滑动

1 用户上滑页面 滚动条触底 开始加载下一页数据
  1 找到滚动条触底事件  微信小程序官方开发文档寻找
  2 判断还有没有下一页数据
    1 获取到总页数  只有总条数
      总页数 = Math.ceil(总条数 /  页容量  pagesize)
      总页数     = Math.ceil( 23 / 10 ) = 3
    2 获取到当前的页码  pagenum
    3 判断一下 当前的页码是否大于等于 总页数 
      表示 没有下一页数据

  3 假如没有下一页数据 弹出一个提示   (  wx.showToast({ title: '没有下一页数据' });显示一会提示框,然后消失   )
  4 假如还有下一页数据 来加载下一页数据
    1 当前的页码 ++
    2 重新发送请求
    3 数据请求回来  要对data中的数组 进行 拼接 而不是全部替换!!!

import { request } from "../../request/index.js";
import regeneratorRuntime from '../../lib/runtime/runtime';
Page({
  data: {
    tabs: [
      {
        id: 0,
        value: "综合",
        isActive: true
      },
      {
        id: 1,
        value: "销量",
        isActive: false
      },
      {
        id: 2,
        value: "价格",
        isActive: false
      }
    ],
    goodsList:[]
  },
  // 接口要的参数
  QueryParams:{
    query:"",
    cid:"",
    pagenum:1,
    pagesize:10
  },
  // 总页数
  totalPages:1,

// 获取商品列表数据
  async getGoodsList(){
    const res=await request({url:"/goods/search",data:this.QueryParams});
    // 获取 总条数
    const total=res.total;
    // 计算总页数
    this.totalPages=Math.ceil(total/this.QueryParams.pagesize);
    // console.log(this.totalPages);
    this.setData({
      // 拼接了数组
      goodsList:[...this.data.goodsList,...res.goods]
    })
 // 关闭下拉刷新的窗口 如果没有调用下拉刷新的窗口 直接关闭也不会报错  
    wx.stopPullDownRefresh();
      
  },


// 页面上滑 滚动条触底事件
  onReachBottom(){
  //  1 判断还有没有下一页数据
    if(this.QueryParams.pagenum>=this.totalPages){
      // 没有下一页数据
      //  console.log('%c'+"没有下一页数据","color:red;font-size:100px;background-image:linear-gradient(to right,#0094ff,pink)");
      wx.showToast({ title: '没有下一页数据' });
        
    }else{
      // 还有下一页数据
      //  console.log('%c'+"有下一页数据","color:red;font-size:100px;background-image:linear-gradient(to right,#0094ff,pink)");
      this.QueryParams.pagenum++;
      this.getGoodsList();
    }
  },


2 下拉刷新页面
  1 触发下拉刷新事件 需要在页面的json文件中开启一个配置项(index.json中加入  "enablePullDownRefresh":true,可以下拉;  "backgroundTextStyle":"dark"下拉效果出现)
    找到 触发下拉刷新的事件
  2 重置 数据 数组 (上拉时拼接了数组,所以需要清除)
  3 重置页码 设置为1
  4 重新发送请求
  5 数据请求回来 需要手动的关闭 等待效果( // 关闭下拉刷新的窗口 如果没有调用下拉刷新的窗口 直接关闭也不会报错  wx.stopPullDownRefresh();)

  // 下拉刷新事件 
  onPullDownRefresh(){
    // 1 重置数组
    this.setData({
      goodsList:[]
    })
    // 2 重置页码
    this.QueryParams.pagenum=1;
    // 3 发送请求
    this.getGoodsList();
  }
})

 

13.正在加载图标

每次请求都会调用加载中的效果,直接将其封装到request中;但是如果同时多个异步请求就需要都请求完毕再取消加载中

complete:()无论成功或者失败都会执行

// 同时发送异步代码的次数
let ajaxTimes=0;
export const request=(params)=>{

  ajaxTimes++;
  // 显示加载中 效果
  wx.showLoading({
    title: "加载中",
    mask: true
  });
    

  // 定义公共的url
  const baseUrl="https://api.zbztb.cn/api/public/v1";
  return new Promise((resolve,reject)=>{
    wx.request({
     ...params,
     url:baseUrl+params.url,
     success:(result)=>{
       resolve(result.data.message);
     },
     fail:(err)=>{
       reject(err);
     },
     complete:()=>{
      ajaxTimes--;
      if(ajaxTimes===0){
        //  关闭正在等待的图标
        wx.hideLoading();
      }
     }
    });
  })
}

 

14.优化动态渲染

优化页面需要的属性

有的iphone不能识别.webp的图片

// 获取商品详情数据
  async getGoodsDetail(goods_id) {
    const goodsObj = await request({ url: "/goods/detail", data: { goods_id } });
    this.GoodsInfo = goodsObj;
    // 1 获取缓存中的商品收藏的数组
    let collect = wx.getStorageSync("collect") || [];
    // 2 判断当前商品是否被收藏
    let isCollect = collect.some(v => v.goods_id === this.GoodsInfo.goods_id);
    this.setData({
      goodsObj: {
        goods_name: goodsObj.goods_name,
        goods_price: goodsObj.goods_price,
        // iphone部分手机 不识别 webp图片格式 
        // 最好找到后台 让他进行修改 
        // 临时自己改 确保后台存在 1.webp => 1.jpg 
        goods_introduce: goodsObj.goods_introduce.replace(/\.webp/g, '.jpg'),
        pics: goodsObj.pics
      },
      isCollect
    })
  },

 

15.购物车底部工具栏

期间遇到问题:

按购物车的时候跳转的是tabbar页面,所以navigate不能跳转,故加上open-type="switchTab"

<!-- 购物网站的底部工具栏 -->
<view class="btm_tool">
  <view class="tool_item">
    <view class="iconfont icon-kefu"></view>
    <view>客服</view>
  </view>
  <view class="tool_item">
    <view class="iconfont icon-yixianshi-"></view>
    <view>分享</view>
  </view>
 <navigator
  open-type="switchTab" class="tool_item" url="/pages/cart/index">
    <view class="iconfont icon-gouwuche"></view>
    <view>购物车</view>
  </navigator>
  <view class="tool_item btn_cart" bindtap="handleCartAdd">
    <view>加入购物车</view>
  </view>
  <view class="tool_item btn_buy">
    <view>立即购买</view>
  </view>
</view>



page {
    // 和.btm_tool 的height一致,否则下面会覆盖
    padding-bottom: 90rpx;
}

.btm_tool {
    // 固定定位
    position: fixed;
    left: 0;
    bottom: 0;
    width: 100%;
    height: 90rpx;
    background-color: #fff;
    display: flex;

    .tool_item {
        flex: 1;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
    }

    .btn_cart {
        flex: 2;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        background-color: #ffa500;
        color: #fff;
        font-size: 30rpx;
        font-weight: 600;
    }

    .btn_buy {
        flex: 2;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        background-color: #eb4450;
        color: #fff;
        font-size: 30rpx;
        font-weight: 600;
    }
}

点击加入购物车

  1 先绑定点击事件
  2 获取缓存中的购物车数据 数组格式 
  3 先判断 当前的商品是否已经存在于 购物车
  4 已经存在 修改商品数据  执行购物车数量++ 重新把购物车数组 填充回缓存中
  5 不存在于购物车的数组中 直接给购物车数组添加一个新元素 新元素 带上 购买数量属性 num  重新把购物车数组 填充回缓存中
  6 弹出提示

  // 点击 加入购物车
  handleCartAdd() {
    // 1 获取缓存中的购物车 数组
    let cart = wx.getStorageSync("cart") || [];
    // 2 判断 商品对象是否存在于购物车数组中
    let index = cart.findIndex(v => v.goods_id === this.GoodsInfo.goods_id);
    if (index === -1) {
      //3  不存在 第一次添加
      this.GoodsInfo.num = 1;
      this.GoodsInfo.checked = true;
      cart.push(this.GoodsInfo);
    } else {
      // 4 已经存在购物车数据 执行 num++
      cart[index].num++;
    }
    // 5 把购物车重新添加回缓存中
    wx.setStorageSync("cart", cart);
    // 6 弹窗提示
    wx.showToast({
      title: '加入成功',
      icon: 'success',
      // true 防止用户 手抖 疯狂点击按钮 
      mask: true
    });



  },

 

获取收货地址:当第一次用户未授权,后面想授权却不能时,如何获取收货地址

1 获取用户的收货地址
  1 绑定点击事件
  2 调用小程序内置 api  获取用户的收货地址  wx.chooseAddress

  2 获取 用户 对小程序 所授予 获取地址的  权限 状态 scope
    1 假设 用户 点击获取收货地址的提示框 确定  authSetting scope.address 
      scope 值 true 直接调用 获取收货地址
    2 假设 用户 从来没有调用过 收货地址的api 
      scope undefined 直接调用 获取收货地址
    3 假设 用户 点击获取收货地址的提示框 取消   
      scope 值 false 
      1 诱导用户 自己 打开 授权设置页面(wx.openSetting) 当用户重新给与 获取地址权限的时候 
      2 获取收货地址
    4 把获取到的收货地址 存入到 本地存储中 

简化前代码

handleChooseAddress() {
    //1.获取权限状态
    wx.getSetting({
      success: (result)=>{
        // 2.获取权限状态 主要发现一些属性名很怪异的时候, 都要使用[]形式来获取属性值
        const scopeAddress = result.authSetting["scope.address"];
        if (scopeAddress === true || scopeAddress === underfined) {
          wx.chooseAddress({
            success: (result1) => {
              console.log(result1);
            }
          });
        } else {
          // 3.用户以前拒绝过授予权限 先诱导用户打开授权页面
          wx.openSetting({
            success: (result2) => {
              // 4.可以调用 收货地址代码
              wx.chooseAddress({
                success: (result3) => {
                  console.log(result3);
                }
              })
            }
          })
        }
      },
    });

 

获取权限状态 主要发现一些属性名很怪异的时候 都要使用[]形式来获取属性值

代码太多嵌套,简化代码,在utils中创建asyncWx.js来封装原生微信小程序api

      调用小程序内置的选择图片api:wx.chooseImage

      准备上传图片 到专门的图片服务器   ;上传文件的 api(uploadFile)   不支持 多个文件同时上传  遍历数组 挨个上传 ;显示正在等待的图片

asyncWx.js

/**
 * promise 形式  getSetting
 */
export const getSetting=()=>{
  return new Promise((resolve,reject)=>{
    wx.getSetting({
      success: (result) => {
        resolve(result);
      },
      fail: (err) => {
        reject(err);
      }
    });
  })
}
/**
 * promise 形式  chooseAddress
 */
export const chooseAddress=()=>{
  return new Promise((resolve,reject)=>{
    wx.chooseAddress({
      success: (result) => {
        resolve(result);
      },
      fail: (err) => {
        reject(err);
      }
    });
  })
}

/**
 * promise 形式  openSetting
 */
export const openSetting=()=>{
  return new Promise((resolve,reject)=>{
    wx.openSetting({
      success: (result) => {
        resolve(result);
      },
      fail: (err) => {
        reject(err);
      }
    });
  })
}

/**
 *  promise 形式  showModal
 * @param {object} param0 参数
 */
export const showModal=({content})=>{
  return new Promise((resolve,reject)=>{
    wx.showModal({
      title: '提示',
      content: content,
      success :(res) =>{
        resolve(res);
      },
      fail:(err)=>{
        reject(err);
      }
    })
  })
}


/**
 *  promise 形式  showToast
 * @param {object} param0 参数
 */
export const showToast=({title})=>{
  return new Promise((resolve,reject)=>{
    wx.showToast({
      title: title,
      icon: 'none',
      success :(res) =>{
        resolve(res);
      },
      fail:(err)=>{
        reject(err);
      }
    })
  })
}

/**
 * promise 形式  login
 */
export const login=()=>{
  return new Promise((resolve,reject)=>{
    wx.login({
      timeout:10000,
      success: (result) => {
        resolve(result);
      },
      fail: (err) => {
        reject(err);
      }
    });
  })
}

/**
 * promise 形式的 小程序的微信支付
 * @param {object} pay 支付所必要的参数
 */
export const requestPayment=(pay)=>{
  return new Promise((resolve,reject)=>{
   wx.requestPayment({
      ...pay,
     success: (result) => {
      resolve(result)
     },
     fail: (err) => {
       reject(err);
     }
   });
     
  })
}

简化后代码:

.js文件:
import {
  getSetting,
  chooseAddress,
  openSetting
} from "../../utils/asyncWx";
import regeneratorRuntime from '../../lib/runtime/runtime';
Page({
  // 点击 收获地址
  async handleChooseAddress() {
    try {
      // 1 获取 权限状态
      const res1 = await getSetting();
      const scopeAddress = res1.authSetting["scope.address"];
      // 2 判断 权限状态
      if (scopeAddress === false) {
        await openSetting();
      }
      // 4 调用获取收货地址的 api
      let address = await chooseAddress();
      address.all = address.provinceName + address.cityName + address.countyName + address.detailInfo;

      // 5 存入到缓存中
      wx.setStorageSync("address", address);

    } catch (error) {
      console.log(error);
    }
  },
})

 

购物车界面

        计算全选:  every数组方法 会遍历会接收一个回调函数,那么每一个回调函数都返回true,那么every方法的返回值为true;只要有一个回调函数返回了false ,那么不再循环执行,直接返回false

                           eg:const allChecked=cart.length?cart.every(v=>v.checked):false;

       弹窗提示(showModal)

      状态提示(showToast)

       跳转支付页面:wx.navigateTo({url:'/pages/pay/index'}    wx.navigateBack({delta:1})跳回刚才跳转页面 

 

支付页面

       微信支付

           1 哪些人 哪些帐号 可以实现微信支付

                  1 企业帐号     2 企业帐号的小程序后台中 必须 给开发者 添加上白名单 

           1 一个 appid 可以同时绑定多个开发者

           2 这些开发者就可以公用这个appid 和 它的开发权限

      

   支付按钮

       1 先判断缓存中有没有token

       2 没有 跳转到授权页面 进行获取token 

       3 有token 。。。

       4 创建订单 获取订单编号

       5 已经完成了微信支付

       6 手动删除缓存中 已经被选中了的商品 

       7 删除后的购物车数据 填充回缓存

       8 再跳转页面 

    支付功能:

           获取用户信息 

<button type="primary" plain open-type="getUserInfo" bindgetuserinfo="handlegetUserInfo">
  获取授权
</button>
import { request } from "../../request/index.js";
import regeneratorRuntime from '../../lib/runtime/runtime';
import { login } from "../../utils/asyncWx.js";

Page({
  // 获取用户信息
  async handleGetUserInfo(e) {
    try {
      
    // 1 获取用户信息
    const { encryptedData, rawData, iv, signature } = e.detail;
    // 2 获取小程序登录成功后的code
    const { code } = await login();
    const loginParams={ encryptedData, rawData, iv, signature ,code};
    //  3 发送请求 获取用户的token
    const {token}=await request({url:"/users/wxlogin",data:loginParams,method:"post"});
    // 4 把token存入缓存中 同时跳转回上一个页面
    wx.setStorageSync("token", token);
    wx.navigateBack({
      delta: 1
    });
  
    } catch (error) {
      console.log(error);
    }
  }
})

       支付流程:

              发起微信支付  await requestPayment(pay);


import { getSetting, chooseAddress, openSetting, showModal, showToast, requestPayment } from "../../utils/asyncWx.js";
import regeneratorRuntime from '../../lib/runtime/runtime';
import { request } from "../../request/index.js";
Page({
  data: {
    address: {},
    cart: [],
    totalPrice: 0,
    totalNum: 0
  },
  onShow() {
    // 1 获取缓存中的收货地址信息
    const address = wx.getStorageSync("address");
    // 1 获取缓存中的购物车数据
    let cart = wx.getStorageSync("cart") || [];
    // 过滤后的购物车数组
    cart = cart.filter(v => v.checked);
    this.setData({ address });

    // 1 总价格 总数量
    let totalPrice = 0;
    let totalNum = 0;
    cart.forEach(v => {
      totalPrice += v.num * v.goods_price;
      totalNum += v.num;
    })
    this.setData({
      cart,
      totalPrice, totalNum,
      address
    });
  },
  // 点击 支付 
  async handleOrderPay() {
    try {

      // 1 判断缓存中有没有token 
      const token = wx.getStorageSync("token");
      // 2 判断
      if (!token) {
        wx.navigateTo({
          url: '/pages/auth/index'
        });
        return;
      }
      // 3 创建订单
      // 3.1 准备 请求头参数
      // const header = { Authorization: token };
      // 3.2 准备 请求体参数
      const order_price = this.data.totalPrice;
      const consignee_addr = this.data.address.all;
      const cart = this.data.cart;
      let goods = [];
      cart.forEach(v => goods.push({
        goods_id: v.goods_id,
        goods_number: v.num,
        goods_price: v.goods_price
      }))
      const orderParams = { order_price, consignee_addr, goods };
      // 4 准备发送请求 创建订单 获取订单编号
      const { order_number } = await request({ url: "/my/orders/create", method: "POST", data: orderParams });
      // 5 发起 预支付接口
      const { pay } = await request({ url: "/my/orders/req_unifiedorder", method: "POST", data: { order_number } });
      // 6 发起微信支付 
      await requestPayment(pay);
      // 7 查询后台 订单状态
      const res = await request({ url: "/my/orders/chkOrder", method: "POST", data: { order_number } });
      await showToast({ title: "支付成功" });
      // 8 手动删除缓存中 已经支付了的商品
      let newCart=wx.getStorageSync("cart");
      newCart=newCart.filter(v=>!v.checked);
      wx.setStorageSync("cart", newCart);
        
      // 8 支付成功了 跳转到订单页面
      wx.navigateTo({
        url: '/pages/order/index'
      });
        
    } catch (error) {
      await showToast({ title: "支付失败" })
      console.log(error);
    }
  }

})

 

 

搜索功能:

        细节问题:

               value.trim()检测输入值的合法性(不为空)

              每次发送请求都会发送很多,eg:发送xiaomi,会发送x,xi,xia,xiao,xiaom,xiaomi多个请求(问题解决方法:防抖)

       解决:

              1 输入框绑定 值改变事件 input事件

                      1 获取到输入框的值

                      2 合法性判断 

                      3 检验通过 把输入框的值 发送到后台

                     4 返回的数据打印到页面上

            2 防抖 (防止抖动) 定时器  节流 

                     0 防抖 一般 输入框中 防止重复输入 重复发送请求

                     1 节流 一般是用在页面下拉和上拉 

                     1 定义全局的定时器id

<view class="search_row">
  <input value="{{inpValue}}" placeholder="请输入您要搜索的商品" bindinput="handleInput"> </input>
  <button bindtap="handleCancel" hidden="{{!isFocus}}">取消</button>
</view>
<view class="search_content">
  <navigator url="/pages/goods_detail/index?goods_id={{item.goods_id}}" class="search_item" wx:for="{{goods}}" wx:key="goods_id">
    {{item.goods_name}}
  </navigator>
</view> 
import { request } from "../../request/index.js";
import regeneratorRuntime from '../../lib/runtime/runtime';
Page({
  data: {
    goods:[],
    // 取消 按钮 是否显示
    isFocus:false,
    // 输入框的值
    inpValue:""
  },
  TimeId:-1,
  // 输入框的值改变 就会触发的事件
  handleInput(e){
    // 1 获取输入框的值
    const {value}=e.detail;
    // 2 检测合法性
    if(!value.trim()){
      this.setData({
        goods:[],
        isFocus:false
      })
      // 值不合法
      return;
    }
    // 3 准备发送请求获取数据
    this.setData({
      isFocus:true
    })
    clearTimeout(this.TimeId);
    this.TimeId=setTimeout(() => {
      this.qsearch(value);
    }, 1000);
  },
  // 发送请求获取搜索建议 数据
  async qsearch(query){
    const res=await request({url:"/goods/qsearch",data:{query}});
    console.log(res);
    this.setData({
      goods:res
    })
  },
  // 点击 取消按钮
  handleCancel(){
    this.setData({
      inpValue:"",
      isFocus:false,
      goods:[]
    })
  }
})

 

意见反馈页面:微信小程序有自己提供

 

小程序发布之后:

用户能搜索小程序需要做:在小程序版本管理中  提交审核 ,几个小时审核通过后可搜索

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值