【19】黑马优购商城:01-项目介绍、初始化项目、首页、优化、分类页面、interceptor 拦截器、搜索、商品列表、商品详情、加入购物车、购物车、确认订单、订单支付、订单列表、发布小程序

本文档详细介绍了如何从零开始构建一个名为“黑马优购”的小程序项目,涵盖项目初始化、页面设计、前后端交互等核心内容。通过实战演练,读者可以深入理解小程序的开发流程和技术要点。

文章目录

黑马优购

1. 项目介绍

首页、分类、搜索、商品列表、商品详情、购物车、支付

2. 初始化项目

2.1 初始化项目

  1. 运行 wepy init standard heima_ugo 命令,初始化小程序项目
  2. 运行 cd heima_ugo 进入项目根目录
  3. 运行 npm install 安装所有依赖项
  4. 运行 wepy build --watch 命令,开启 wepy 项目的实时编译功能
  5. 打开微信开发者工具,加载 wepy 项目并查看效果
  6. 解决 ESLint 语法报错问题

2.2 梳理项目结构

  1. 清理并重置 src -> pages -> index.wpy 首页
  2. 在根目录的 .prettierrc 配置文件内,新增 "semi": false 配置,防止每次格式化代码,添加分号的问题
  3. 清理并重置 src -> app.wpy 中的代码,将 stylescript 标签中,不必要的代码删除掉
  4. 清空 src -> componentssrc -> mixins 目录
  5. 将梳理完毕后的项目,上传至码云

2.3 绘制 tabBar

  1. 新建 src -> pages -> tabs 文件夹,用来存放所有 tabBar 相关的页面

  2. 删除 src -> pages -> index.wpy 页面,并在 tabs 目录中,新建 home.wpycates.wpysearch.wpycart.wpyme.wpy 五个 tabBar 相关的页面

  3. 将页面路径,记录到 src -> app.wpy 文件的 config -> pages 节点中,示例代码如下:

    pages: [
      'pages/tabs/home',
      'pages/tabs/cates',
      'pages/tabs/search',
      'pages/tabs/cart',
      'pages/tabs/me'
    ]
    
  4. 新建 src -> assets 目录,并将素材中的 icons 文件夹,拷贝到项目 src -> assets 目录中

  5. src -> app.wpy 文件中,新增 tabBar 节点,并做如下配置:

    tabBar: {
         
         
      // 选中的文本颜色
      selectedColor: '#D81E06',
      // tabBar 的列表
      list: [
        {
         
         
          // 页面路径
          pagePath: 'pages/tabs/home',
          // 显示的文本
          text: '首页',
          // 默认图标
          iconPath: '/assets/icons/home.png',
          // 选中图标
          selectedIconPath: '/assets/icons/home-active.png'
        },
        {
         
         
          pagePath: 'pages/tabs/cates',
          text: '分类',
          iconPath: '/assets/icons/cates.png',
          selectedIconPath: '/assets/icons/cates-active.png'
        },
        {
         
         
          pagePath: 'pages/tabs/search',
          text: '搜索',
          iconPath: '/assets/icons/search.png',
          selectedIconPath: '/assets/icons/search-active.png'
        },
        {
         
         
          pagePath: 'pages/tabs/cart',
          text: '购物车',
          iconPath: '/assets/icons/cart.png',
          selectedIconPath: '/assets/icons/cart-active.png'
        },
        {
         
         
          pagePath: 'pages/tabs/me',
          text: '我的',
          iconPath: '/assets/icons/my.png',
          selectedIconPath: '/assets/icons/my-active.png'
        }
      ]
    }
    

2.4 修改导航栏样式

打开 src -> app.wpy 文件,找到 window 节点,并配置如下:

window: {
   
   
  // 页面背景色
  backgroundTextStyle: 'dark',
  // 导航条背景色
  navigationBarBackgroundColor: '#D81E06',
  // 导航条标题文本
  navigationBarTitleText: '黑马优购',
  // 导航条标题文字颜色
  navigationBarTextStyle: 'white'
}

3. 首页

3.1 为异步 API 启用 Promise 功能

  1. 打开 src -> app.wpy 文件

  2. 找到 constructor() 构造函数

  3. 在构造函数的最后,新增如下代码:

    constructor() {
         
         
        super()
        this.use('requestfix')
        // 通过下面这一行代码,可以为异步的API,
        // 开启Promise功能,这样,异步API调用的结果,返回值是Promise对象
        this.use('promisify')
    }
    

3.2 轮播图数据渲染

  1. 获取轮播图数据

      // 获取轮播图数据的函数
    async getSwiperData() {
         
         
        const {
         
          data: res } = await wepy.get('/home/swiperdata')
    
        if (res.meta.status !== 200) {
         
         
          return wepy.baseToast()
        }
    
        this.swiperList = res.message
        this.$apply()
    }
    
  2. 使用 wepy.showToast() 弹框提示

  3. 使用 swiper 组件和 swiper-item 组件渲染轮播图效果

  4. 使用 navigator 组件将 images 图片包裹起来,从而点击图片实现跳转

    <!-- 轮播图区域 -->
    <swiper circular indicator-dots>
      <swiper-item wx:for="{
          
          {swiperList}}" wx:key="index">
        <navigator url="{
          
          {item.navigator_url}}" open-type="{
          
          {item.open_type}}">
          <image src="{
          
          {item.image_src}}" />
        </navigator>
      </swiper-item>
    </swiper>
    
  5. 设置 swiper 组件的高度为 350rpx 从而实现轮播图在不同屏幕的自适应

    swiper {
      height: 350rpx;
      navigator,
      image {
        height: 100%;
        width: 750rpx;
      }
    }
    

3.3 获取首页分类选项数据

  // 获取首页分类相关的数据项
async getCateItems() {
   
   
    const {
   
    data: res } = await wepy.get('/home/catitems')

    if (res.meta.status !== 200) {
   
   
      return wepy.baseToast()
    }

    this.cateItems = res.message
    this.$apply()
}

3.4 渲染分类数据项对应的UI结构

<!-- 分类区域 -->
<view class="cates">
  <block wx:for="{
    
    {cateItems}}" wx:key="index">
    <navigator url="/pages/tabs/cates" open-type="{
    
    {item.open_type}}" wx:if="{
    
    {item.navigator_url !== undefined}}" hover-class="none">
      <image src="{
    
    {item.image_src}}" />
    </navigator> 

    <image src="{
    
    {item.image_src}}" wx:else/>
  </block>
</view>

3.5 美化分类数据项的UI显示效果

.cates {
  display: flex;
  justify-content: space-around;
  margin: 40rpx 0;
  image {
    width: 128rpx;
    height: 140rpx;
  }
}

3.6 获取楼层相关的数据

onLoad() {
   
   
    this.getSwiperData()
    this.getCateItems()
    // 在页面加载完成后,自动获取楼层数据
    this.getFloorData()
}

// 获取楼层相关的数据
async getFloorData() {
   
   
    const {
   
    data: res } = await wepy.get('/home/floordata')

    if (res.meta.status !== 200) {
   
   
      return wepy.baseToast()
    }

    this.floorData = res.message
    // 通知页面,data中数据发生了变化,需要强制页面重新渲染一次
    this.$apply()
}

3.7 渲染楼层UI结构

<!-- 楼层区域 -->
<view class="floor-container">
  <view class="floor-item" wx:for="{
    
    {floorData}}" wx:key="index">
    <!-- 楼层的标题 -->
    <image class="floor-item-title" src="{
    
    {item.floor_title.image_src}}"/>
    <!-- 楼层的图片 -->
    <view class="floor-img-box">
      <image class="floor-item-pic" wx:for="{
    
    {item.product_list}}" wx:key="index" src="{
    
    {item.image_src}}" style="width: {
      
      {
      
      item.image_width}}rpx;" @tap="goGoodsList({
    
    {item.navigator_url}})"/>
    </view>
  </view>
</view>

3.8 美化楼层UI结构

.floor-container {
  .floor-item {
    .floor-item-title {
      height: 50rpx;
      width: 640rpx;
      display: block;
    }
    .floor-img-box {
      .floor-item-pic {
        float: left;
        height: 190rpx;
        margin: 8rpx;
        margin-top: 0;
        &:nth-child(1) {
          height: 390rpx;
        }
      }
    }
  }
}

3.9 点击楼层图片跳转到商品列表页面

methods = {
   
   
    // 点击楼层中的每一张图片,都要跳转到商品列表页面
    goGoodsList(url) {
   
   
      wepy.navigateTo({
   
   
        url
      })
    }
}

4. 优化

4.1 把页面的业务逻辑抽离到单独的 mixin 文件中

为了精简每个小程序页面的代码,可以将 script 中的业务逻辑,抽离到对应的 mixin 文件中,具体步骤:

  1. src -> mixins 文件夹中,新建与页面路径对应的 .js 文件,并初始化基本的代码结构如下:

    import wepy from 'wepy'
    
    // 注意,必须继承自 wepy.mixin
    export default class extends wepy.mixin {
         
         }
    
  2. 在对应的页面中,可以导入并使用对应的 mixin,具体代码如下:

    <script>
    import wepy from 'wepy'
    // 1. 导入外界的 mixin 文件,并接受
    // @ 就代表 src 这一层路径
    import mix from '@/mixins/tabs/home.js'
    
    export default class extends wepy.page {
         
         
      // 2. 把导入的 mix 对象,挂载到 mixins 这个数据中就行
      mixins = [mix]
    }
    </script>
    

4.2 封装 baseToast 函数提示错误消息

  1. 为了提高项目的维护性、可用性、扩展性,可以将常用的 js 逻辑,封装到 src -> baseAPI.js 文件中:

    import wepy from 'wepy'
    
    /**
     * 弹框提示一个无图标的 Toast 消息
     * @str 要提示的消息内容
     */
    wepy.baseToast = function(str = '获取数据失败!') {
         
         
      wepy.showToast({
         
         
        title: str,
        // 弹框期间不会携带任何图标
        icon: 'none',
        duration: 1500
      })
    }
    
  2. app.wpy 中导入执行 baseAPI.js 文件中的代码:

    <script>
        import wepy from 'wepy'
        import 'wepy-async-function'
        // 导入并执行 baseAPI.js 中的所有代码
        import '@/baseAPI.js'
    </script>
    

4.3 封装 wepy.get 函数发起get请求

在小程序项目中,需要经常发起数据请求,因此,可以将 wepy.request() 函数封装,在全局挂在 wepy.get() 函数,从而发起 Get 请求,代码如下:

// src/baseAPI.js

import wepy from 'wepy'

// 请求根路径
const baseURL = 'https://www.zhengzhicheng.cn/api/public/v1'

/**
 * 发起 get 请求的 API
 * @url 请求的地址,为相对路径,必须以 / 开头
 * @data 请求的参数对象
 */
wepy.get = function(url, data = {
   
   }) {
   
   
  return wepy.request({
   
   
    url: baseURL + url,
    method: 'GET',
    data
  })
}

4.4 封装 wepy.post 函数发起get请求

在小程序项目中,需要经常发起数据请求,因此,可以将 wepy.request() 函数封装,在全局挂在 wepy.post() 函数,从而发起 Post 请求,代码如下:

// src/baseAPI.js

import wepy from 'wepy'

// 请求根路径
const baseURL = 'https://www.zhengzhicheng.cn/api/public/v1'

/**
 * 发起 post 请求的 API
 * @url 请求的地址,为相对路径,必须以 / 开头
 * @data 请求的参数对象
 */
wepy.post = function (url, data = {
   
   }) {
   
     
  return wepy.request({
   
   
    url: baseURL + url,
    method: 'POST',
    data
  })
}

5. 分类页面

5.1 自定义分类页面的编译模式

  1. 点击工具栏中,编译模式的下拉菜单,选择新建编译模式
  2. 填写编译模式的名称
  3. 选择启动页面的路径
  4. 确认添加

5.2 获取分类数据列表

async getCateList() {
   
   
    const {
   
    data: res } = await wepy.get('/categories')

    if (res.meta.status !== 200) {
   
   
      return wepy.baseToast()
    }

    this.cateList = res.message
    this.secondCate = res.message[0].children
    this.$apply()
}

5.3 下载并安装 vant 小程序UI组件库

  1. 访问 vant-weapp 的 Github 主页 https://github.com/youzan/vant-weapp
  2. 点击 Clone or Download 按钮
  3. 选择 Download ZIP
  4. 解压下载的 vant-weapp-dev.zip
  5. 进入解压后的目录,将 lib 目录重命名为 vant
  6. 把重命名为 vant 的目录,复制到 src -> assets 目录中

5.4 将 vant 中的徽章组件注册为全局组件

  1. 打开 app.wpy 文件

  2. config 节点内,新增 usingComponents 节点,具体代码如下:

    config = {
         
         
        // 引用并注册全局组件
        usingComponents: {
         
         
          // 徽章组件
          'van-badge': './assets/vant/badge/index',
          'van-badge-group': './assets/vant/badge-group/index'
        }
    }
    

5.5 渲染左侧的一级分类列表结构

<van-badge-group active="{
    
    { active }}" bind:change="onChange">
    <van-badge title="{
    
    {item.cat_name}}" wx:for="{
    
    {cateList}}" wx:key="index" />
</van-badge-group>

5.6 使用 scroll-view 优化左侧分类的滚动效果

<!-- 左侧的滚动视图区域 -->
<scroll-view class="left" scroll-y style="height: 200px;">
  <van-badge-group active="{
    
    { active }}" bind:change="onChange">
    <van-badge title="{
    
    {item.cat_name}}" wx:for="{
    
    {cateList}}" wx:key="index" />
  </van-badge-group>
</scroll-view>

5.7 动态获取窗口的可用高度

onLoad() {
   
   
    // 动态获取屏幕可用的高度
    this.getWindowHeight()
    this.getCateList()
}
  
    // 动态获取屏幕可用的高度
async getWindowHeight() {
   
   
    const res = await wepy.getSystemInfo()
    if (res.errMsg === 'getSystemInfo:ok') {
   
   
      this.wh = res.windowHeight
      this.$apply()
    }
}

5.8 根据一级分类的变化动态切换二级分类数据

  methods = {
   
   
    onChange(e) {
   
   
      // e.detail 是点击项的索引
      // console.log(e.detail)
      this.secondCate = this.cateList[e.detail].children
    }
  }

5.9 渲染二级和三级分类的UI结构

<!-- 右侧滚动视图区域 -->
<scroll-view class="right" scroll-y style="height: {
      
      {
      
      wh}}px;">
  <!-- 循环创建二级分类 -->
  <block wx:for="{
    
    {secondCate}}" wx:key="index">
    <van-row>
      <van-col span="24" style="text-align:center;">
        <text class="cate_title" space="ensp">/  {
  
  {item.cat_name}}  /</text>
      </van-col>
    </van-row>
    <!-- 三级分类 -->
    <van-row>
      <block wx:for="{
    
    {item.children}}" wx:key="index">
        <van-col span="8" class="cell" @tap="goGoodsList({
    
    {item.cat_id}})">
          <image src="{
    
    {item.cat_icon}}" class="thumbImg" />
          <view class="thumbTitle">{
  
  {item.cat_name}}</view>
        </van-col>
      </block>
    </van-row>
  </block>
</scroll-view>

5.10 点击三级分类跳转到商品列表页面

  methods = {
   
   
    // 点击跳转到商品列表页面,同时把商品分类的 cid 传递过去
    goGoodsList(cid) {
   
   
      wepy.navigateTo({
   
   
        url: '/pages/goods_list?cid=' + cid
      })
    }
  }

6. interceptor 拦截器

6.1 介绍 wepy 中的拦截器

可以使用WePY提供的全局拦截器对原生API的请求进行拦截。

具体方法是配置API的config、fail、success、complete回调函数。参考示例:

import wepy from 'wepy';

export default class extends wepy.app {
   
   
    constructor () {
   
   
        // this is not allowed before super()
        super();
        // 拦截request请求
        this.intercept('request', {
   
   
            // 发出请求时的回调函数
            config (p) {
   
   
                // 对所有request请求中的OBJECT参数对象统一附加时间戳属性
                p.timestamp = +new Date();
                console.log('config request: ', p);
                // 必须返回OBJECT参数对象,否则无法发送请求到服务端
                return p;
            },

            // 请求成功后的回调函数
            success (p) {
   
   
                // 可以在这里对收到的响应数据对象进行加工处理
                console.log('request success: ', p);
                // 必须返回响应数据对象,否则后续无法对响应数据进行处理
                return p;
            },

            //请求失败后的回调函数
            fail (p) {
   
   
                console.log('request fail: ', p);
                // 必须返回响应数据对象,否则后续无法对响应数据进行处理
                return p;
            },

            // 请求完成时的回调函数(请求成功或失败都会被执行)
            complete (p) {
   
   
                console.log('request complete: ', p);
            }
        });
    }
}

6.2 实现数据加载期间的loading效果

打开 app.wpy,在 constructor() 构造函数中,通过拦截器实现loading效果,具体代码如下:

constructor() {
   
   
    super()
    this.use('requestfix')
    // 通过这一行代码,可以为异步的API,开启Promise功能,这样,异步API调用的结果,返回值是Promise对象
    this.use('promisify')

    // 拦截器
    this.intercept('request', {
   
   
      // 发出请求时的回调函数
      config(p) {
   
   
        // 显示loading效果
        wepy.showLoading({
   
   
          title: '数据加载中...'
        })
        // 必须返回OBJECT参数对象,否则无法发送请求到服务端
        return p
      },

      // 请求成功后的回调函数
      success(p) {
   
   
        // 必须返回响应数据对象,否则后续无法对响应数据进行处理
        return p
      },

      // 请求失败后的回调函数
      fail(p) {
   
   
        // 必须返回响应数据对象,否则后续无法对响应数据进行处理
        return p
      },

      // 请求完成时的回调函数(请求成功或失败都会被执行)
      complete(p) {
   
   
        // 隐藏loading效果
        wepy.hideLoading()
    
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值