小程序项目20220120

一.小程序的第三方框架

1.腾讯 wepy (类似vue)
2.美团 mpvue (类似vue)
3.京东 taro (类似react)
4.滴滴 chameleon
5.uni-app (类似vue)
6.原生框架 MINA

二.接口文档

后端接口文档地址

三.项⽬搭建

1.新建⼩程序项⽬

当时还报了个这个错

微信小程序报错:Unhandled promise rejection TypeError: WebAssembly.instantiate(): Argument 0 must be a buffe
在这里插入图片描述

解决:
选择这个版本即可:
在这里插入图片描述

2.搭建⽬录结构

在这里插入图片描述

3.引⼊字体图标

阿里巴巴矢量图标库 iconfont

添加图标的步骤

4.搭建项⽬tabbar结构

app.json配置tabBar这个对象

  "tabBar": {
    "color": "#999",
    "selectedColor": "#ff2d4a",
    "backgroundColor": "#fafafa",
    "position": "bottom",
    "borderStyle": "black",
    "list": [
      {
        "pagePath": "pages/index/index",
        "text": "首页",
        "iconPath": "icons/home.png",
        "selectedIconPath": "icons/home-o.png"
      },
      {
        "pagePath": "pages/category/index",
        "text": "分类",
        "iconPath": "icons/category.png",
        "selectedIconPath": "icons/category-o.png"
      },
      {
        "pagePath": "pages/cart/index",
        "text": "购物车",
        "iconPath": "icons/cart.png",
        "selectedIconPath": "icons/cart-o.png"
      },
      {
        "pagePath": "pages/user/index",
        "text": "我的",
        "iconPath": "icons/my.png",
        "selectedIconPath": "icons/my-o.png"
      }
    ]
  }

四.初始化样式

app.wxss里面设置全局样式

1)注意:在微信小程序中 不支持 通配符*

如果你要使用的话,只能手动地将一个一个标签写上去,然后写样式

page,view,text,swiper,swiper-item,image,navigator{
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}

2)自定义颜色

主题颜色 通过变量来实现

  1. less 中 存在 变量这个知识
  2. 原生的css和wxss也是支持 变量

app.wxss里面定义

page{
   /* 定义主题颜色 */
   --themeColor:#eb4450;
 }

index.wxss就可以直接使用

view{
    /* 使用主题颜色 */
    color: var(--themeColor);
}

3)全局字体

定义统一字体大小 假设设计稿 大小是 375px
因为:1px= 2rpx
那么:14px = 28rpx
app.wxss里面定义

page{
   font-size: 28rpx;
 }

因为page是跟标签,设置样式以后,会全局生效

五.首页

1.搜索框

由于搜索框在很多页面都有使用到,所以把我们封装成一个自定义组件

1)新建自定义组件

①在vscode里面新建一个SearchInput文件夹
在这里插入图片描述
②在微信开发者工具中,右击SearchInput文件夹,选择新建Component,名字和文件夹一致,那么会自动生成一堆文件
在这里插入图片描述
自动生成的文件:
在这里插入图片描述

2)引入自定义组件

哪个页面需要引用,就在哪个页面的.json文件里面进行声明
SearchInput.wxml自定义内容

<!--components/SearchInput/SearchInput.wxml-->
<text>我是搜索框</text>

我们需要在首页使用,就在首页的index.json引入SearchInput

{
  "navigationBarTitleText":"优购首页",
  "usingComponents": {
    "SearchInput":"../../components/SearchInput/SearchInput"
  }
}

然后在index.wxml就可以直接使用

<!--index.wxml-->
<view >
  <SearchInput ></SearchInput>
</view>

2.轮播图

1)获取异步数据

    wx.request({
        url:'https://api-hmugo-web.itheima.net/api/public/v1/home/swiperdata',
        success:(res)=>{
            console.log(res)
        }
    })

如果提示你没有权限请求这个异步接口,可能是这两个原因:
①你的项目没有配置appId
在这里插入图片描述

②没有把域名添加到小程序后台的白名单当中,也没有权限请求这个接口
解决:
第一种方式:
勾选上“不校验合法域名”,勾上以后局可以随便的发送域名
在这里插入图片描述
如果后期项目上线,要求请求必须是合法的:①域名必须是https的,②把域名添加到小程序后台里面

第二种方式:
把域名添加到小程序后台里面
步骤:
进入网址:https://mp.weixin.qq.com/
在这里插入图片描述
登陆以后,开发-> 开发管理 -> 开发设置 ->服务器域名 -> request合法域名
在这里插入图片描述
服务器域名里面添加或者修改域名
在这里插入图片描述

2)轮播图调样式

这样直接写

<!-- 搜索框结束 -->
  <view class="index_swiper">
    <swiper  >
      <swiper-item wx:for="{{swiperList}}" wx:key="goods_id">
        <navigator>
          <image src="{{item.image_src}}"></image>
        </navigator>
      </swiper-item>
    </swiper>
  </view>
  <!-- 搜索框结束 -->

出来了轮播图,但由于存在默认样式
在这里插入图片描述
主要原因:
1 swiper标签存在默认的宽度和高度
100% * 150px
2 image标签也存在默认的宽度和高度
320px * 240px

分析:
1 先看一下原图的宽高 750 * 340
2 让图片的高度自适应 宽度 等于100%
3 让swiper标签的高度 变成和图片的高一样即可

解决方案:
①设置image 图片标签 mode属性 渲染模式
widthFix 让图片的标签宽为默认值320px,高度自适应 (也就是按照比例正常展示出高度)

<image mode="widthFix" src="{{item.image_src}}"></image>

在这里插入图片描述
②此时image的父级是宽慰100%,那么我们把image的宽设为100%

/* pages/search/index.wxss */
.index_swiper{
    swiper{
        image{
            width: 100%;
        }
    }
}

在这里插入图片描述

③此时还有一个问题,就是调整手机大小屏的时候,发现swiper标签的高度也是写死的,需要调整。
在这里插入图片描述
解决:让swiper标签的高度 变成和图片的高一样即可
由于图片适配后的宽高是 375 * 170,那么swiper按照这个一起来

/* pages/search/index.wxss */
.index_swiper{
    swiper{
        width: 750rpx;
        height: 340rpx;
        image{
            width: 100%;
        }
    }
}

在这里插入图片描述
④最后再加一点轮播图的自动轮播等属性就ok啦

  <!-- 搜索框结束 -->
  <view class="index_swiper">
    <swiper  autoplay indicator-dots circular>
      <swiper-item wx:for="{{swiperList}}" wx:key="goods_id">
        <navigator url="{{item.navigator_url}}">
          <image mode="widthFix" src="{{item.image_src}}"></image>
        </navigator>
      </swiper-item>
    </swiper>
  </view>
  <!-- 搜索框结束 -->

3)异步数据优化

原本是这样:
首页的js文件

    wx.request({
        url:'https://api-hmugo-web.itheima.net/api/public/v1/home/swiperdata',
        success:(res)=>{
            this.setData({
                swiperList: res.data.message
              })
        }
    })

弊端:会存在回调地域的问题

优化:
首页的js文件

import {request} from '../../request/index.js'
request({url:'https://api-hmugo-web.itheima.net/api/public/v1/home/swiperdata'})
    .then(res =>{
      console.log(111111)
      this.setData({
          swiperList: res.data.message
        })
    })

封装的requst.js

export const request=(params)=>{
    return new Promise((resolve,reject)=>{
        wx.request({
            ...params,
            success:(res)=>{
                resolve(res)
            },
            fail:(err)=>{
                reject(err)
            }
        })
    })
}

3.楼层

1)楼层的数据是这样的

在这里插入图片描述

2)wxml进行数据 循环:

<!-- 楼层开始 -->
  <view class="index_floor">
    <view class="floor_group" 
    wx:for="{{floorList}}" 
    wx:key="name" 
    wx:for-item="item1" 
    wx:for-index="index1">
      <!-- 标题 -->
      <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 src="{{item2.image_src}}" mode="widthFix"></image>
        </navigator>
      </view>
    </view>
  </view>
  <!-- 楼层结束 -->

3)效果:这些图片同宽度相同,但高矮不同

在这里插入图片描述

4)完善下样式

①后四个超链接的高度为第一个图高度的一半
原图的宽高 232 *386
232 / 386 = 33.33vw / height
第一张图片的高度 height:33.33vw * 386 / 232

②2 3 两个超链接下面弄得缝隙padding

    .floor_list{
      // 清除浮动
      overflow: hidden;
      navigator{
        float: left;
        width: 33.33%;

      /* 后四个超链接 */
      &:nth-last-child(-n+4){
        //后几个就是 (-n+几)
      height:33.33vw * 386 /  232 /2 ;
      border-left: 10rpx solid #fff;
      }

      /* 2 3 两个超链接 */
      &:nth-child(2),
      &:nth-child(3){
        border-bottom: 10rpx solid #fff;
      }
        image{
          width: 100%;
          height: 100%;
        }
      }
    }
  }

③第一章图片的mode="widthFix",其他的为mode="scaleToFill"

 <image mode="{{index2===0?'widthFix':'scaleToFill'}}" src="{{item2.image_src}}" ></image>

六.分类页面

效果:
在这里插入图片描述
问题:
开发“分类页面”,在组件编辑的时候,小程序会自动刷新到首页,这样会影响开发的效率,怎么办?

解决:
①普通编译 -> 添加编译模式
在这里插入图片描述
②修改“编译名称”(可以中文)和启动页面(路径)
在这里插入图片描述

1.获取数据

1)数据和页面的关系

在这里插入图片描述

2)获取数据

 data: {
    // 左侧的菜单数据
    leftMenuList: [],
    // 右侧的商品数据
    rightContent: []
},
// 接口的返回数据
Cates: [],
 
//获取分类的数据
getCates(){
    request({
      url:'https://api-hmugo-web.itheima.net/api/public/v1/categories'
    }).then(res=>{
      this.Cates = res.data.message;
      // 构造左侧的大菜单数据
      let leftMenuList = this.Cates.map(v => v.cat_name);
      // 构造右侧的商品数据
      let rightContent = this.Cates[0].children;
      this.setData({
        leftMenuList,
        rightContent
      })
    })
  }

3)页面的实现

①使用微信小程序的scroll-view

在这里插入图片描述
添加scroll-y属性才支持上下滚动

②代码中的知识点:

1.保留关键字 *this 代表在 for 循环中的 item 本身,这种表示需要 item 本身是一个唯一的字符串或者数字
2.less中使用calc的时候要注意

height: ~'calc( 100vh - 90rpx )'
或者
height: calc(~"100% - 90rpx");

编译后:

height: calc(100% - 90rpx);

3.父级设置为display:flex;,子项 高度 100%默认高度为100%
4.text标签前后不能换行
开始是这样写的:

 <view class="goods_title">
   <text class="delimiter">/</text>
   <text class="title">
        {{item1.cat_name}}
   </text>
   <text class="delimiter">/</text>
</view>

效果很奇怪,三个text标签的行内标签对不齐
在这里插入图片描述
后面这样写:

 <view class="goods_title">
      <text class="delimiter">/</text>
      <text class="title">{{item1.cat_name}}</text>
      <text class="delimiter">/</text>
</view>

效果就正常啦!
在这里插入图片描述

③CSS中的CurrentColor

他表示“当前文字的颜色”。它是CSS3新增的关键字,IE9+及其他相应版本的现代浏览器才能正确识别它。
在这里插入图片描述

④点击左边标题,左边文字实现高亮和右边数据的更新

index.wxml:

<!-- 左侧菜单 -->
      <scroll-view scroll-y class="left_menu">
          <view class="menu_item {{index == currentIndex ? 'active':''}}" 
          wx:for="{{leftMenuList}}" 
          wx:key="*this"
          bindtap="handleItemTap"
          data-index="{{index}}">
                {{item}}
          </view>
      </scroll-view>

index.js:

data: {
    leftMenuList:[],
    rightContent:[],
    currentIndex:0
},

handleItemTap(e){
    const { index } = e.currentTarget.dataset;
    let rightContent = this.Cates[index].children;
    this.setData({
      rightContent,
      currentIndex:index
    })
  }

index.less:

.left_menu{
    flex: 2;
    .menu_item{
        height: 80rpx;
        display: flex;
        justify-content: center;
        align-items: center;
        font-size: 30rpx;
      }
     .active{
        color: var(--themeColor);
        border-left: 5rpx solid currentColor;
      }
}
④切换侧边栏标题,右边数据每次置顶

如果直接这样写没效果

<scroll-view scroll-y class="right_content" scroll-top="0"></scroll-view>

需要写成变量的形式:
正确的:
index.xwml

<scroll-view scroll-y class="right_content" scroll-top="{{scrollTop}}"></scroll-view>

index.js:

data: {
    scrollTop:0  //初始化
  },
//左侧点击标题触发
handleItemTap(e){
    const { index } = e.currentTarget.dataset;
    let rightContent = this.Cates[index].children;
    this.setData({
      rightContent,
      currentIndex:index,
      // 重新设置 右侧内容的scroll-view标签的距离顶部的距离
      scrollTop: 0
    })
  }

2.优化性能:缓存

思路:

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

注意:

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

代码:

index.js:

onLoad: function (options) {
    //  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
        })
      }
    }

  },
  //获取分类的数据
  getCates(){
    request({
      url:'https://api-hmugo-web.itheima.net/api/public/v1/categories'
    }).then(res=>{
      this.Cates = res.data.message;
      // 把接口的数据存入到本地存储中
      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
      })
    })
  },
  //获取右侧数据和当前的index索引
  handleItemTap(e){
    const { index } = e.currentTarget.dataset;
    let rightContent = this.Cates[index].children;
    this.setData({
      rightContent,
      currentIndex:index
    })
  }

3.优化接口代码

request文件夹下的Index.js里面:

export const request=(params)=>{
    return new Promise((resolve,reject)=>{
        let baseUrl = 'https://api-hmugo-web.itheima.net/api/public/v1';
        wx.request({
            ...params,
            url:baseUrl + params.url,
            success:(res)=>{
                resolve(res.data.message)
            },
            fail:(err)=>{
                reject(err)
            }
        })
    })
}

使用:

  getSwiperList(){
    request({url:'/home/swiperdata'})
    .then(res =>{
      this.setData({
          swiperList: res
        })
    })
  },

4.使用async语法

使用async在部分机型会报错,所以在正式环境中不能随便使用es7的语法,因为小程序只有es6转es5,没有es7转es5的。
提示的错误是:regeneratorRuntime is not defined;
小程序中支持es7的async语法,es7的async号称是解决回调地域的最终解决方案
步骤:
1.在小程序的开发工具中,勾选es6转es5语法
在这里插入图片描述

2.下载facebook的regenerator库中的[regenerator/packages/regenerator-runtime/runtime.js](https://github.com/facebook/regenerator/blob/5703a79746fffc152600fdcef46ba9230671025a/packages/regenerator-runtime/runtime.js)
注意:这里的给出的库是指定版本的,不要下载最新的。
3. 在小程序目录下新建文件夹 lib/runtime/runtime.js,将代码拷贝进去。
4. 在每一个需要使用async语法的页面js文件中,都引入(不能全局引入),只需要引入,不需要调用。
import regeneratorRuntime from '../../lib/runtime/runtime';

然后就可以正常的使用了async语法处理异步请求了。

七.商品列表

在这里插入图片描述

1.获取分类id

商品列表页面是在商品分类页面,点击商品进入的。需要带参数
在这里插入图片描述

那如何传参数呢?

cid就是分类页面的cat_id
categoryindex.wxml页面

<view class="goods_list">
      <navigator wx:for="{{item1.children}}" wx:for-item="item2" wx:for-index="index2" wx:key="cat_id"   url="/pages/goods_list/index?cid={{item2.cat_id}}">
       <image mode="widthFix" src="{{item2.cat_icon}}"></image>
       <view class="goods_name">{{item2.cat_name}}</view>
       </navigator>
</view>

如何查看接收的参数?

第一种方法:
goods_list的index.js页面的onLoad生命周期里面可以接收

/**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    console.log(options)
  },

在这里插入图片描述
第二种方法:
在这里插入图片描述

在当前页面开发,如何设置并且启动参数?

像下面这样设置,一直刷新编译,商品列表的参数都会一直在
在这里插入图片描述

2.实现tab页面

1)goods_list的页面(父组件)

goods_listindex.wsml

<SearchInput></SearchInput>
<Tabs tabs="{{tabs}}" bindtabsItemChange="handleTabsItemChange">
    <block wx:if="{{tabs[0].isActive}}">
        1
    </block>
    <block wx:if="{{tabs[1].isActive}}">
        2
    </block>
    <block wx:if="{{tabs[2].isActive}}">
        3
    </block>
</Tabs>

goods_listindex.js

// pages/goods_list/index.js
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,
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    this.QueryParams.cid = options.cid||""
    this.QueryParams.query = options.query||""
    this.getGoodsList()
  },
  //获取商品列表的数据
  async getGoodsList(){
    const res = await request({url:'/goods/search',data:this.QueryParams})
    
  },
    handleTabsItemChange(e){
    const index = e.detail;
    let {tabs} = this.data;
    tabs.forEach((v,i) => 
      i == index ? v.isActive = true : v.isActive = false
    );
    this.setData({
      tabs
    })
  },
})

要点提取:
1.统计组件之间参数传递,通过在onLoad生命周期里面获取参数
2.父组件通过bindtabsItemChange传值给子组件tabs数据

2)封装一个tabs组件(子组件)

tabs文件夹下的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>

tabs文件夹下的index.js

// components/Tabs/Tabs.js
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    tabs:{
      type:Array,
      value:[]
    }
  },
  /**
   * 组件的方法列表
   */
  methods: {
    handleItemTap(e){
      const {index} = e.currentTarget.dataset;
      this.triggerEvent("tabsItemChange",index)
    }
  }
})

要点提取:
①子组件通过properties接收父组件传过来的值
②子传父:子组件通过this.triggerEvent("tabsItemChange",index)触发父组件的自定义方法tabsItemChange,传参数index。父组件通过bindtabsItemChange触发handleTabsItemChange方法,里面写逻辑.

3.tabs里面内容的展示

goods_list文件夹下的index.wxml

<SearchInput></SearchInput>
<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">
                <!-- 左侧 -->
                <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">
                    <vie class="goods_name">{{item.goods_name}}</vie>
                    <vie class="goods_price">¥{{item.goods_price}}</vie>
                </view>
            </navigator>
        </view>
    </block>
    <block wx:if="{{tabs[1].isActive}}">
        2
    </block>
    <block wx:if="{{tabs[2].isActive}}">
        3
    </block>
</Tabs>

4.启动上拉页面功能

加载下一页,onReachBottom页面触底事件

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

3.假如没有下一页数据 弹出一个提示
4.假如还有下一页数据 来加载下一页数据
1 当前的页码 ++
2 重新发送请求
3 数据请求回来 要对data中的数组 进行 拼接 而不是全部替换!!!

goods_list文件夹下的index.js

  // 总页数
  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]
    })
  },
  // 页面上滑 滚动条触底事件
  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();
    }
  },

5.启动下拉页面功能

①页面的json文件中开启设置enablePullDownRefresh:true
②页面的js中,绑定事件onPullDownRefresh

步骤:下拉刷新页面

  1. 触发下拉刷新事件 需要在页面的json文件中开启一个配置项
    找到 触发下拉刷新的事件
  2. 重置 数据 数组
  3. 重置页码 设置为1
  4. 重新发送请求
  5. 数据请求回来 需要手动的关闭 等待效果

goods_list文件夹的index.json

{
  "usingComponents": {
    "SearchInput":"../../components/SearchInput/SearchInput",
    "Tabs":"../../components/Tabs/Tabs"
  },
  "navigationBarTitleText":"商品列表",
  "enablePullDownRefresh":true, //设置下拉刷新
  "backgroundTextStyle":"dark" //设置下拉刷新样式
}

goods_list文件夹的index.js
数据获取成功以后可以手动关闭下拉刷新的窗口

  // 获取商品列表数据
  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();   
  },

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

八.商品详情页

在这里插入图片描述

1.轮播图

1)展示,样式微调

<view class="detail_swiper">
    <swiper autoplay circular indicator-dots>
        <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>
    </swiper>
</view>

由于swiperimage都有默认样式,所以进行样式设置:
设置swiper的高为65vw,宽默认为100%;
设置image的宽度为60%,宽度通过mode="widthFix"自适应

.detail_swiper{
    swiper{
        height: 65vw;
        text-align: center;
        swiper-item {
            image{
                width: 60%;
            }
        }
    }
}

效果:图片内容区域比较小,所以让图片的宽度设为60%
在这里插入图片描述

2)点击轮播图 放大预览

wx.previewImage
在新页面中全屏预览图片。预览的过程中用户可以进行保存图片、发送给朋友等操作。

// 点击轮播图 放大预览
  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
    });

  },

打印看了下urlscurrent的值
在这里插入图片描述

2.图文详情

图文详情是以富文本的形式展示内容,因为他是html的格式返回的
属性nodes表示节点列表/HTML

<rich-text nodes="{{goodsObj.goods_introduce}}"></rich-text>

3.客服,分享,购物车

1)样式效果:

在这里插入图片描述
样式完成了:
①整个部分进行fixed定位,宽高写死
②整个部分flex布局,前三个的fixed设为1,后两个的fixed设为2
③前三个里面分别再进行flex布局,这个是上下的布局,目的是为了居中显示的

2)客服,分享的专属功能

客服,分享的专属功能:必须要在button按钮上才能完成,所以写个按钮,通过绝对定位分别写在客服和分享上,并且将它隐藏掉。

<view class="tool_item">
   <view class="iconfont icon-kefu"></view>
   <view>客服</view>
   <button open-type="contact"></button>
</view>
<view class="tool_item">
    <view class="iconfont icon-yixianshi-"></view>
    <view>分享</view>
    <button open-type="share"></button>
</view>

样式:

.tool_item{
        flex: 1;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        font-size: 24rpx;
        position: relative;//定位
        button{
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            opacity: 0;//设为透明
        }
}

3)点击购物车跳转到tabBar页面

跳转到tabBar页面不能用navigateTo,可以用switchTab,他可以跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面

    <navigator open-type="switchTab" url="/pages/cart/index" class="tool_item" >
        <view class="iconfont icon-gouwuche"></view>
        <view>购物车</view>
    </navigator>

4.通过页面栈获取参数

onShow的生命周期里面获取

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
    let pages = getCurrentPages();
    let currentPage = pages[pages.length - 1];
    let options = currentPage.options;
    const { goods_id } = options;
    this.getGoodsDetail(goods_id)//获取页面信息的接口
  },

5.加入购物车

思路:该功能 是否加入了购物车,加了几次,都是通过缓存进行存值的
①去缓存取值,没值默认为空数组。
判断缓存中数据的goods_id与当前页面数据的goods_id是否为同一个值?
不是:就是第一次添加 num 设为0;
是:就 num +1
②更新缓存num的值
③弹窗提示成功,注意防止手抖

  // 点击 加入购物车
  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
    });



  },

6.收藏或取消收藏商品

思路:也是通过缓存判断是否被收藏
①取缓存数据,若缓存有数据表示已被收藏,没有数据表示没有被收藏
若已经被收藏了,去掉当前数据(splice)
若没有被收藏,加上当前数据(push)
②更新collect缓存
③更新dataisCollect的数据,因为页面需要显示根据这个字段展示不同图标,数据也需要更新同步

 /**
   * 页面的初始数据
   */
  data: {
    goodsObj: {},
    // 商品是否被收藏
    isCollect:false
  },
  // 商品对象
  GoodsInfo: {},

  // 点击 商品收藏图标
  handleCollect(){
    let isCollect=false;
    // 1 获取缓存中的商品收藏数组
    let collect=wx.getStorageSync("collect")||[];
    // 2 判断该商品是否被收藏过
    let index=collect.findIndex(v=>v.goods_id===this.GoodsInfo.goods_id);
    // 3 当index!=-1表示 已经收藏过 
    if(index!==-1){
      // 能找到 已经收藏过了  在数组中删除该商品
      collect.splice(index,1);
      isCollect=false;
      wx.showToast({
        title: '取消成功',
        icon: 'success',
        mask: true
      });
        
    }else{
      // 没有收藏过
      collect.push(this.GoodsInfo);
      isCollect=true;
      wx.showToast({
        title: '收藏成功',
        icon: 'success',
        mask: true
      });
    }
    // 4 把数组存入到缓存中
    wx.setStorageSync("collect", collect);
    // 5 修改data中的属性  isCollect
    this.setData({
      isCollect
    })
      
      
  }

九.购物车

1.获取地址授权(有两个版本)

1)老版本:

微信小程序权限解析

  • 思路
    点击按钮“获取地址” ————> 弹窗(是否允许获取用户地址)————>“是”:能获取到地址。并且下次点击按钮都可以重复获取
    ————> “否”:不能获取地址,并且下次点击按钮也无法获取

  • 针对第一次点击了“否”,下次想获取地址,如何解决?
    回答:
    先诱导用户打开授权界面wx.openSetting
    然后调用收获地址代码wx.chooseAddress

  • 那如何知道用户以前是否点击了弹窗,并且点击的是什么按钮呢?(“是”,“否”,没点过)
    ①通过wx.getSetting获取权限状态
    根据返回的result.authSetting["scope.address"]的boolean值来判断是否有权限
    true:表示已经被授权
    false:表示被拒绝
    undefined:表示还没有到授权这一步(true和false说明前面已经点击过这个按钮了, undefied说明这次是第一次点击按钮)

②true和undefined都可以直接获取地址
通过wx.chooseAddress

③false 说明用户 以前拒绝过授权授予权限
通过wx.openSetting打开设置页面
然后再wx.chooseAddress获取地址

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

cart文件夹下的index.js

import { getSetting, chooseAddress, openSetting} from "../../utils/asyncWx.js";
import regeneratorRuntime from '../../lib/runtime/runtime';

  // 点击 收货地址
  async handleChooseAddress() {
    try {
      // 1 获取 权限状态
      const res1 = await getSetting();
      const scopeAddress = res1.authSetting["scope.address"];
      // 2 判断 权限状态
      if (scopeAddress === false) {
       let address = await openSetting();
        console.log(address)
      }
      // 4 调用获取收货地址的 api
      let address = await chooseAddress();
      console.log(address)

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



2)新版本

存在的问题:使用wx.getSetting获取到的地址权限scope.address为什么一直是true?也就是说没法判断权限状态。
官方公告写啦,9.25起这三个接口不用授权直接返回true。

是不是那就不要做太多的判断啦😄!

  handleChooseAddress(){
    wx.chooseAddress({
      success:(result1)=>{
        console.log(result1)
      }
    })
  }

2.判断回显数据

有个知识点:对象 空对象 bool类型也是true
所以不能直接判断 address的boolean值,可以判断他下面的属性是否存在。

<!-- 收货地址 -->
<view class="revice_address_row">
  <!-- 当收货地址 不存在 按钮显示  对象 空对象 bool类型也是true  -->
  <view class="address_btn" wx:if="{{!address.userName}}"  >
    <button bindtap="handleChooseAddress" type="primary"  plain  >获取收货地址</button>
  </view>
  <!-- 当收货地址 存在 详细信息就显示 -->
  <view wx:else  class="user_info_row" >
    <view class="user_info">
      <view>{{address.userName}}</view>
      <view>{{address.all}}</view>
    </view>
    <view class="user_phone">{{address.telNumber}}</view>
  </view>
</view>

3.提取封装(总价,数量)

因为下面用到的计算勾选的商品总价和商品数量的方法比较多
①通过forEach遍历,itemchecked为true的时候,就可以叠加进去;
itemchecked为false的时候,就全选的allChecked字段为false
②更新setData的数据
③更新cart的数据缓存

  // 设置购物车状态同时 重新计算 底部工具栏的数据 全选 总价格 购买的数量
  setCart(cart) {
    let allChecked = true;
    // 1 总价格 总数量
    let totalPrice = 0;
    let totalNum = 0;
    cart.forEach(v => {
      if (v.checked) {
        totalPrice += v.num * v.goods_price;
        totalNum += v.num;
      } else {
        allChecked = false;
      }
    })
    // 判断数组是否为空
    allChecked = cart.length != 0 ? allChecked : false;
    this.setData({
      cart,
      totalPrice, totalNum, allChecked
    });
    wx.setStorageSync("cart", cart);
  },

4.点击商品前面的复选框

通过对比goods_id, 在cart数据中找点击的商品的index,然后通过索引取反checked的值。

// 商品的选中
  handeItemChange(e) {
    // 1 获取被修改的商品的id
    const goods_id = e.currentTarget.dataset.id;
    // 2 获取购物车数组 
    let { cart } = this.data;
    // 3 找到被修改的商品对象
    let index = cart.findIndex(v => v.goods_id === goods_id);
    // 4 选中状态取反
    cart[index].checked = !cart[index].checked;
	// 5.重新计算总价和总数量
    this.setCart(cart);

  },

4.点击全选按钮

全选字段allChecked的boolean值取反,通过forEachchecked的字段全部改为一致。

  // 商品全选功能
  handleItemAllCheck() {
    // 1 获取data中的数据
    let { cart, allChecked } = this.data;
    // 2 修改值
    allChecked = !allChecked;
    // 3 循环修改cart数组 中的商品选中状态
    cart.forEach(v => v.checked = allChecked);
    // 4 把修改后的值 填充回data或者缓存中
    this.setCart(cart);
  },

5.编辑

就是点击的这个
通过传过来的operation参数判断是还是
通过goods_id对比,在cart中找到对应索引,对数据num字段进行修改

  // 商品数量的编辑功能
  async handleItemNumEdit(e) {
    // 1 获取传递过来的参数 
    const { operation, id } = e.currentTarget.dataset;
    // 2 获取购物车数组
    let { cart } = this.data;
    // 3 找到需要修改的商品的索引
    const index = cart.findIndex(v => v.goods_id === id);
    // 4 判断是否要执行删除
    if (cart[index].num === 1 && operation === -1) {
      // 4.1 弹窗提示
      const res = await showModal({ content: "您是否要删除?" });
      if (res.confirm) {
        cart.splice(index, 1);
        this.setCart(cart);
      }
    } else {
      // 4  进行修改数量
      cart[index].num += operation;
      // 5 设置回缓存和data中
      this.setCart(cart);
    }
  },

十.支付

1.支付权限的分析

哪些人 哪些帐号 可以实现微信支付
1. appId的账号必须是:企业帐号
2. 开发者 必须要被 该企业帐号的小程序后台中 添加上白名单
1 ) 一个 appid 可以同时绑定多个开发者
2 )这些开发者就可以公用这个appid 和 它的开发权限

官网文档搜索:支付 ——> wx.requestPayment ———> 微信支付接口文档

想要实现微信支付也是有自己的后台实现微信支付,完成的流程也很繁琐。我们只需要考虑纯前端的角度即可。

2.支付流程:

支付按钮
1 先判断缓存中有没有token
2 没有 跳转到授权页面 进行获取token
3 有token 。。。
4 创建订单 获取订单编号
5 已经完成了微信支付
6 手动删除缓存中 已经被选中了的商品
7 删除后的购物车数据 填充回缓存
8 再跳转页面

在这里插入图片描述

思路:支付页面,没有token,授权,获取token
后端要求这些参数:
在这里插入图片描述

1) 授权页点击按钮,获取token,存入缓存:

在这里插入图片描述
pages/auth/index.js

import { request } from "../../request/index.js";
import regeneratorRuntime from '../../lib/runtime/runtime';
import {login} from '../../utils/asyncWx.js'
Page({
  async handleGetUserInfo(e){
    try{
      const {encryptedData,rawData,iv,signature} = e.detail;
      const {code} =await login()
      const params = {encryptedData,rawData,iv,signature,code}
      const {token} = await request({ url: "/users/wxlogin", data: params,method:"post"});
      wx.setStorageSync("token",token)
      wx.navigatorTo({
        delta:1
      })
    }catch(e){
      console.log(e)
    }
  }
})

2)异步请求封装 优化


// 同时发送异步代码的次数
let ajaxTimes=0;
export const request=(params)=>{
  // 判断 url中是否带有 /my/ 请求的是私有的路径 带上header token
  let header={...params.header};
  if(params.url.includes("/my/")){
    // 拼接header 带上token
    header["Authorization"]=wx.getStorageSync("token");
  }


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

  // 定义公共的url
  const baseUrl="https://api-hmugo-web.itheima.net/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();
      }
     }
    });
  })
}

3)点击支付

  // 点击 支付 
  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);
    }
  }

十一.个人中心

1.头像的展示和样式的处理

判断是否有用户信息的图片,有就展示头像,没有就只展示登录按钮

<view class="user_info_wrap">
    <view wx:if="{{userinfo.avatarUrl}}" class="user_img_wrap">
        <image class="user_bg" src="{{userinfo.avatarUrl}}"></image>
        <view class="user_info">
            <image class="user_icon" src="{{userinfo.avatarUrl}}"></image>
            <view class="user_name">{{userinfo.nickName}}</view>
        </view>
    </view>
    <view wx:else class="user_btn">
        <navigator url="/pages/login/index">登录</navigator>
    </view>
</view>

样式:需要注意的:filter: blur(10rpx);用来设置模糊

/* pages/user/index.wxss */
page{
    background-color: #edece8;
  }
  .user_info_wrap {
    height: 45vh;
    overflow: hidden;
    background-color: var(--themeColor);
    position: relative;
    .user_img_wrap {
      position: relative;
      .user_bg {
        height: 50vh;
        // 高斯模糊
        filter: blur(10rpx);
      }
      .user_info {
        position: absolute;
        left: 50%;
        transform: translateX(-50%);
        top: 20%;
        text-align: center;
        .user_icon{
          width: 150rpx;
          height: 150rpx;
          border-radius: 50%;
        }
        .user_name{
          color: #fff;
          margin-top: 40rpx;
          // font-size: 40rpx;
        }
      }
    }
    .user_btn{
      position: absolute;
      left: 50%;
      transform: translateX(-50%);
      top: 40%;
      border: 1rpx solid greenyellow;
      color: greenyellow;
      font-size: 38rpx;
      padding: 30rpx;
      border-radius: 10rpx;
    }
  }

有信息:展示头像
在这里插入图片描述
无信息:
在这里插入图片描述

2.没有登录信息的去授权页面获取信息

登录的wxml:

<button type="primary" plain open-type="getUserInfo" bindgetuserinfo="handleGetUserInfo" > 登录 </button>

登录的js:通过navigateBack自动返回大上一页

// pages/login/index.js
Page({
  handleGetUserInfo(e){
    const {userInfo}=e.detail;
    wx.setStorageSync("userinfo", userInfo);
    wx.navigateBack({
      delta: 1
    });
      
  }
})

十二.订单详情

1.订单模块的逻辑

1 页面被打开的时候 onShow
0 onShow 不同于onLoad 无法在形参上接收 options参数
0.5 判断缓存中有没有token
1 没有 直接跳转到授权页面
2 有 直接往下进行
1 获取url上的参数type
2 根据type来决定页面标题的数组元素 哪个被激活选中
2 根据type 去发送请求获取订单数据
3 渲染页面
2 点击不同的标题 重新发送请求来获取和渲染数据

2.页面效果

在这里插入图片描述

3.“我的”的wxml页面:

<!-- 我的订单 -->
    <view class="orders_wrap">
      <view class="orders_title">我的订单</view>
      <view class="order_content">
        <navigator url="/pages/order/index?type=1">
          <view class="iconfont icon-ding_dan"></view>
          <view class="order_name">全部订单</view>
        </navigator>
        <navigator url="/pages/order/index?type=2">
          <view class="iconfont icon-fukuantongzhi"></view>
          <view class="order_name">待付款</view>
        </navigator>
        <navigator url="/pages/order/index?type=3">
          <view class="iconfont icon-receipt-address"></view>
          <view class="order_name">待收货</view>
        </navigator>
        <navigator>
          <view class="iconfont icon-tuihuotuikuan_dianpu"></view>
          <view class="order_name">退款/退货</view>
        </navigator>
      </view>
    </view>

4.“我的”的js页面:

/* 
1 页面被打开的时候 onShow 
  0 onShow 不同于onLoad 无法在形参上接收 options参数 
  0.5 判断缓存中有没有token 
    1 没有 直接跳转到授权页面
    2 有 直接往下进行 
  1 获取url上的参数type
  2 根据type来决定页面标题的数组元素 哪个被激活选中 
  2 根据type 去发送请求获取订单数据
  3 渲染页面
2 点击不同的标题 重新发送请求来获取和渲染数据 
 */

import { request } from "../../request/index.js";
import regeneratorRuntime from '../../lib/runtime/runtime';

Page({

  /**
   * 页面的初始数据
   */
  data: {
    orders: [],
    tabs: [
      {
        id: 0,
        value: "全部",
        isActive: true
      },
      {
        id: 1,
        value: "待付款",
        isActive: false
      },
      {
        id: 2,
        value: "待发货",
        isActive: false
      },
      {
        id: 3,
        value: "退款/退货",
        isActive: false
      }
    ]
  },

  onShow(options) {
    const token = wx.getStorageSync("token");
    if (!token) {
      wx.navigateTo({
        url: '/pages/auth/index'
      });
      return;
    }



    // 1 获取当前的小程序的页面栈-数组 长度最大是10页面 
    let pages = getCurrentPages();
    // 2 数组中 索引最大的页面就是当前页面
    let currentPage = pages[pages.length - 1];
    // 3 获取url上的type参数
    const { type } = currentPage.options;
    // 4 激活选中页面标题 当 type=1 index=0 
    this.changeTitleByIndex(type-1);
    this.getOrders(type);
  },
  // 获取订单列表的方法
  async getOrders(type) {
    const res = await request({ url: "/my/orders/all", data: { type } });
    this.setData({
      orders: res.orders.map(v=>({...v,create_time_cn:(new Date(v.create_time*1000).toLocaleString())}))
    })
  },
  // 根据标题索引来激活选中 标题数组
  changeTitleByIndex(index) {
    // 2 修改源数组
    let { tabs } = this.data;
    tabs.forEach((v, i) => i === index ? v.isActive = true : v.isActive = false);
    // 3 赋值到data中
    this.setData({
      tabs
    })
  },
  handleTabsItemChange(e) {
    // 1 获取被点击的标题索引
    const { index } = e.detail;
    this.changeTitleByIndex(index);
    // 2 重新发送请求 type=1 index=0
    this.getOrders(index+1);
  }
})

5.“授权”的wxml页面:

<button type="primary" plain open-type="getUserInfo" bindgetuserinfo="handleGetUserInfo" >
  获取授权
</button>
  

6.“支付授权”的js页面:

这个和上面的登录不一样,登录的目的是为了获取昵称和头像,这个要获取token,并且通过token获取后面操作的权限

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);
    }
  }
})

十三.收藏

1.页面样式

在这里插入图片描述

2.“商品收藏”的wxml页面

<Tabs tabs="{{tabs}}" bindtabsItemChange="handleTabsItemChange">

  <view class="collect_main">
    <view class="collect_title">
      <text class="collect_tips active">全部</text>
      <text class="collect_tips">正在热卖</text>
      <text class="collect_tips">即将上线</text>
    </view>
    <view class="collect_content">
      <navigator class="goods_item" wx:for="{{collect}}" 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>
  </view>

</Tabs>

3.“商品收藏”的json:

{
  "usingComponents": {
    "Tabs":"../../components/Tabs/Tabs"
  },
  "navigationBarTitleText":"商品收藏"
}

4.“商品收藏”的js:

// pages/collect/index.js
Page({
  /**
   * 页面的初始数据
   */
  data: {
    collect:[],
    tabs: [
      {
        id: 0,
        value: "商品收藏",
        isActive: true
      },
      {
        id: 1,
        value: "品牌收藏",
        isActive: false
      },
      {
        id: 2,
        value: "店铺收藏",
        isActive: false
      },
      {
        id: 3,
        value: "浏览器足迹",
        isActive: false
      }
    ]
  },
  onShow(){
    const collect=wx.getStorageSync("collect")||[];
    this.setData({
      collect
    });
      
  },
  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
    })
  }
})

十四.搜索中心

1 输入框绑定 值改变事件 input事件
1 获取到输入框的值
2 合法性判断
3 检验通过 把输入框的值 发送到后台
4 返回的数据打印到页面上
2 防抖 (防止抖动) 定时器 节流
0 防抖 一般 输入框中 防止重复输入 重复发送请求
1 节流 一般是用在页面下拉和上拉
1 定义全局的定时器id

1.“搜索”的页面效果

1)引入的效果

在这里插入图片描述

2)点击一下输入框的效果

在这里插入图片描述

3)输入内容以后,右边出现“取消”按钮,下面出现模糊查询的内容

在这里插入图片描述

2.“搜索”的wml页面

<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> 

3.“搜索”的js页面

注意,这里有用到通过定时器处理防抖的方法

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:[]
    })
  }
})

十五.意见反馈

1.样式

这里我们主要看的是textera输入框,上传图片的功能
进入页面的样式:
在这里插入图片描述
上传图片以后,提交之前:
在这里插入图片描述

2.反馈的wxml页面:

<!--pages/feedback/index.wxml-->
<view class="fb_content">
    <textarea value="{{textVal}}" bindinput="handleTextInput" placeholder="请描述一下您的问题"> </textarea>
    <view class="fb_tool">
        <button bindtap="handleChooseImg">+</button>
        <view class="up_img_item" wx:for="{{chooseImgs}}" wx:key="*this" bindtap="handleRemoveImg" data-index="{{index}}">
            <UpImg src="{{item}}"></UpImg>
        </view>
    </view>
</view>
<view class="form_btn_wrap">
    <button bindtap="handleFormSubmit" type="warn">
        <icon type="success_no_circle" size="23" color="white"></icon>
        提交
    </button>

</view>

这里的UpImg是一个自定义组件,展示的是图片的列表

1)自定义组件UpImg的wxml

<view class="up_img_wrap">
    <image src="{{src}}"></image>
    <icon type="clear" size="23" color="red"></icon>
</view>

2)自定义组件UpImg的js

// components/UpImg/UpImg.js
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    src:{
      type:String,
      value:""
    }
  },
})

3)自定义组件UpImg的wxss

/* components/UpImg/UpImg.wxss */
.up_img_wrap{
    width: 90rpx;
    height: 90rpx;
    position: relative;
  }
  .up_img_wrap image{
    width: 100%;
    height: 100%;
    border-radius: 15rpx;
  }
  .up_img_wrap icon{
    position: absolute;
    top:-22rpx;
    right: -22rpx;
  }

3.反馈的js逻辑:

1)textera输入内容是存值,点击按钮获取值

  // 文本域的输入的事件
  handleTextInput(e) {
    this.setData({
      textVal: e.detail.value
    })
  },
    // 提交按钮的点击
  handleFormSubmit() {
    // 1 获取文本域的内容 图片数组
    const { textVal, chooseImgs } = this.data;
    // 2 合法性的验证
    if (!textVal||!textVal.trim()) {
      // 不合法
      wx.showToast({
        title: '输入不合法',
        icon: 'none',
        mask: true
      });
      return;
    }
    }
  }

2)上传图片的js

①点击 “+” 选择图片,调用小程序内置的选择图片api wx.chooseImage
②点击图片,有删除功能
③点击提交按钮,上传图片 到专门的图片服务器。
注意:上传文件的 api 不支持 多个文件同时上传 遍历数组 挨个上传。通过loading提高用户体验

// pages/feedback/index.js
Page({

  /**
   * 页面的初始数据
   */
  data: {
    chooseImgs: []
  },
  // 文本域的输入的事件
  handleTextInput(e) {
    this.setData({
      textVal: e.detail.value
    })
  },
  // 点击 “+” 选择图片
  handleChooseImg() {
    // 2 调用小程序内置的选择图片api
    wx.chooseImage({
      // 同时选中的图片的数量
      count: 9,
      // 图片的格式  原图  压缩
      sizeType: ['original', 'compressed'],
      // 图片的来源  相册  照相机
      sourceType: ['album', 'camera'],
      success: (result) => {
        this.setData({
          // 图片数组 进行拼接 
          chooseImgs: [...this.data.chooseImgs, ...result.tempFilePaths]
        })
      }
    });

  },
  // 点击 自定义图片组件
  handleRemoveImg(e) {
    // 2 获取被点击的组件的索引
    const { index } = e.currentTarget.dataset;
    // 3 获取data中的图片数组
    let { chooseImgs } = this.data;
    // 4 删除元素
    chooseImgs.splice(index, 1);
    this.setData({
      chooseImgs
    })
  },
  // 提交按钮的点击
  handleFormSubmit() {
    // 1 获取文本域的内容 图片数组
    const { textVal, chooseImgs } = this.data;
    // 2 合法性的验证
    if (!textVal||!textVal.trim()) {
      // 不合法
      wx.showToast({
        title: '输入不合法',
        icon: 'none',
        mask: true
      });
      return;
    }
    // 3 准备上传图片 到专门的图片服务器 
    // 上传文件的 api 不支持 多个文件同时上传  遍历数组 挨个上传 
    // 显示正在等待的图片
    wx.showLoading({
      title: "正在上传中",
      mask: true
    });

    // 判断有没有需要上传的图片数组

    if (chooseImgs.length != 0) {
      chooseImgs.forEach((v, i) => {
        wx.uploadFile({
          // 图片要上传到哪里
          url: 'https://images.ac.cn/Home/Index/UploadAction/',
          // 被上传的文件的路径
          filePath: v,
          // 上传的文件的名称 后台来获取文件  file
          name: "file",
          // 顺带的文本信息
          formData: {},
          success: (result) => {
            let url = JSON.parse(result.data).url;
            this.UpLoadImgs.push(url);

            // 所有的图片都上传完毕了才触发  
            if (i === chooseImgs.length - 1) {
              wx.hideLoading();
              //  提交都成功了
              // 重置页面
              this.setData({
                textVal: "",
                chooseImgs: []
              })
              // 返回上一个页面
              wx.navigateBack({
                delta: 1
              });

            }
          }
        });
      })
    } else {
      wx.hideLoading();
      wx.navigateBack({
        delta: 1
      });

    }
  }
})

十六.项目发布

1. “不校验合法域名” 去掉

“详情” ——> “不校验合法域名” 的勾选去掉。
后面发现没有请求权限,需要把微信添加到后台的白名单里面
在这里插入图片描述

2.appId必须是企业或者个人的appId,而不能用测试的。

否则看不到上面的“上传”按钮
在这里插入图片描述

3.整个项目的大小不能超过2M

后期可以通过分包技术解决大小超过2M的问题。可以把整个项目打包成不同的模块,来分别上传,但分包的总大小也不能超过10M。

4.点击“上传” ——> “确定”——>“上传”

在这里插入图片描述

1)版本号:

大版本的更新会对版本号的第一个数字进行修改,如1——>2这样;
第二个数字是对重要功能进行的修改
第二个数字是对最小的功能,如bug优化或者补丁什么的
在这里插入图片描述

2)右侧弹窗可以看到整个项目的大小100K不到

在这里插入图片描述

3)中间有一个弹窗“以下文件没有被打包上传,没关系的,这些事less文件,它本身就是辅助开发的

在这里插入图片描述

5.上传成功以后在小程序官网找到项目

管理 ——> 版本管理
在这里插入图片描述

1)体验版

找到刚提交的,不过现在是“体验版”,里正式版还存在一些距离
在这里插入图片描述

2)发正式版本

步骤:
①点击提交审核“”,是把整个项目提交给小程序的管理人员
②项目就会在“审核版本”这一栏,等待小程序后台的审核,时间大约是几个小时
在这里插入图片描述
③审核完毕以后,就会移到“线上版本”这一栏。那么在手机上搜索应用,就可以找到这个小程序了。
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值