微信小程序-木樨shop

博客班级 https://edu.cnblogs.com/campus/zjcsxy/SE2020
作业要求 https://edu.cnblogs.com/campus/zjcsxy/SE2020/homework/11334
作业目标 1.编写一个小程序,可以全新编写,也可以学习别人的小程序进行修改
2.熟悉git代码管理流程,将源代码上传到到github
3.在博客园班级中写一篇相应的博文
作业源代码 https://github.com/863252054/miniProgram_first
学号 31801140
姓名 徐洋

前言

第一次学习做微信小程序,我的学习方法是看视频,跟着bilibili上的教学视频一步步做。也曾试过直接下载他人github上的代码一点点琢磨其中语句的意思,但由于自己对小程序一无所知,在自己的电脑上根本运行不了,所以这条路直接被我否决。最终还是跟着教学视频一步一个脚印的去学习小程序的写法。该项目为跟着b站上的黑马程序员自己纯手打的,期间遇到好多小问题,但最终解决了其中的大部分,让作业以一个较为完整的方式展现出来。尽管仍有很多疑惑的地方,但作者的编程思路给了我极大的启发

此程序用的是教学视频提供的接口写的,其中数据若想成功获取必须勾选 微信开发工具=>详情=>本地设置=>不校验合法域名、web-view(业务域名) 才能正常加载页面

界面展示






文件结构

  • components存放自定义组件
  • icons存放tabbar的图标
  • pages存放主要界面,其中cart为购物车,category为商品分类,goods_detail存放商品详情,goods_list存放某一商品的各类,index存放首页,login显示登录按钮,pay存放支付页面,user存放个人主页
  • request中只有一个js文件,用来存放promise方法
  • utils存放封装的各种方法


个人经验总结

  • js用来编辑功能代码,存放数据,加载时需要执行的命令,事件触发时执行的事件都放在这里
  • json决定是否使用自定义组件,设置页面样式,能否下拉等动作,其中app.json还可以定义tabbar
  • wxml和html功能类似
  • wxss和css功能类似,我用的vscode编写,在编辑样式时使用了插件,只要编写less文件就能自动生成wxss,使编辑方便了很多

设计逻辑

首页

本小程序中最核心的部分是对于教学视频提供的接口的使用,拿首页举例,要获取轮播图数组,楼层以及分类数组,调用了以下函数(用轮播图数组距离)

  onLoad: function (options) {
    ...
    this.getSwiperList();
    this.getCatesList();
    this.getFloorList();
  },
 
    // 获取轮播图数据
  getSwiperList() {
    request({ url: "/home/swiperdata" })
      .then(result => {
        this.setData({
          swiperList: result
        })
      })
  }     

其中的reuqest为了避免回调地狱使用了promise函数在request.js文件中进行了如下封装

   export const request=(params)=>{
    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,
           url:baseUrl+params.url,
           success:(result)=>{
               resolve(result.data.message);
           },
           fail:(err)=>{
               reject(err);
           },
           complete:()=>{
               ajaxTimes--;
               if(ajaxTimes===0){
            //    关闭正在等待的图标
                wx.hideLoading()
            }
           }
        });
    })
}  

商品分类

分类界面分为左右两条:左侧菜单和右侧商品内容。通过调用接口,拿请求到的数据值来进行构造。左右都用到了循环和scroll-view实现上下滑动。

期间有个小问题就是不一样的菜单下,右侧商品内容有多有少,这就导致了切换菜单时,右侧滚动条不能自动回到顶部,可能卡在中间的位置,造成观感不佳,因此在右侧的scroll-view中添加了scroll-top属性,在data中将其设置初始化为0,每次切换菜单,将其重置为零,这样就实现了切换菜单,商品内容自动回到顶部。

获取接口数据使用了es7 的async await来发送请求,request还是上面贴出来的代码,而调用方式用了新的写法:

 // 获取分类数据
async getCates() {
    const res = await request({ url: "/categories" });
    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
    })
  },

由于接口数据量比较大,为了优化性能,在onLoad时进行了对本地是否有存储的判断,逻辑在注释中已写出

onLoad: function (options) {
    /**
     * 
     * 1 先判断本地存储中是否有旧数据
     *  {time:Date.now(),data:[...]}
     * 2 没有旧数据 直接发送新请求
     * 3 有旧数据 同时没过期 则直接使用
     */
    // 1 获取本地存储中的数据 (小程序中也存在本地存储技术)
    const Cates = wx.getStorageSync("cates");
    // 2 判断
    if (!Cates) {
      // 不存在 发送请求获取数据
      this.getCates();
    } else {
      // 有旧数据  定义过期时间 10s 
      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,
        })
      }
    }
  }

商品列表

在商品列表中与之前一样正常的获取接口数据,不过此处碰到了两个问题:

  • 一页10条信息显示不完,解决办法如下:

    用户上滑页面 滚动条触底 开始加载下一页数据
    
    1. 找到滚动条触底事件
    
    2. 判断还有没有下一页数据
    
       a. 获取到总页数 总条数:total
    
        总页数 = Math.ceil(总条数 / 页容量 pagesize)
    
        b. 获取到当前的页码 pagenum
    
        c. 判断 当前的页码是否大于等于 总页数
    
        表示没有下一页数据
    
    3. 假如没有下一页数据 弹出提示
    
    4. 有的话继续加载
    

  • 接口图片部分缺失,解决办法如下:

​ 三元表达式,若检测有图片,直接显示,否则显示自己替换图片

   <!-- 当接口中的图片丢失时 -->
              <image mode="widthFix" src="{{item.goods_small_logo?item.goods_small_logo:'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1234272396,3380570950&fm=26&gp=0.jpg'}}" ></image>

商品详情

设计思路如下:

1 发送请求数据

2 点击轮播图 预览大图

   a 给轮播图绑定点击事件
    
   b 调用小程序的api previewImage

3 点击 加入购物车

   a 先绑定点击事件
    
   b 获取缓存中的购物车数据 数组格式
    
   c 先判断当前商品是否已经存在于 购物车
    
   d 已经存在 执行购物车数量++ 重新把购物车数组 填充回缓冲中
    
   e 不存在于购物车的数组中 直接给购物车数组添加新元素
    
   f 弹出提示
  
问题:iphone部分手机,不识别 webp图片格式,需要自己修改,并保证后台存在   1.webp=>1.jpg
 goods_introduce: goodsObj.goods_introduce.replace(/\.webp/g, '.jpg'),

其中十分重要的是这一步和下一步的购物车之间有联系,因此得将涉及到的购物车数据写入缓存 中,因此将添加至购物车的代码贴出

  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',
    // mask防止用户手抖,1.5s后才能继续响应
      mask: true
    });
  }

购物车

购物车是此小程序中代码量最大也是逻辑最复杂的部分,设计思路如下:

1 获取用户的收货地址
  1 绑定点击事件
  2 调用小程序内置 api  获取用户的收货地址 wx.chooseAddress
  3 把获取到的信息 存入本地缓存中
2 页面加载完毕
  0 onload onshow
  1 获取本地存储中的地址数据
  2 把数据 设置给data中的一个变量
3 onShow
  0 回到了商品详情页面 第一次添加商品的时候 手动添加了属性
    1 num = 1;
    2 checked = true;
  1 获取缓存中的购物车数组
  2 把购物车数据 填充到data中
4 全选的实现 数据的展示
  1 onShow 获取缓存中的购物车数组
  2 根据购物车中的商品数据 所有商品都被选中 全选就被选中
5 总价格和总数量
  1 都需要商品被选中 我们才拿它来计算
  2 获取购物车数组
  3 遍历
  4 判断商品是否被选中
  5 总价格 += 商品的单价 * 商品的数量
    总数量 += 商品的数量
  6 把计算后的价格和数量 设置回data中即可
6 商品的选中
  1 绑定change事件
  2 获取到被修改的商品对象
  3 商品对象的选中状态 取反
  4 重新填充回data中和缓存中
  5 重新计算全选,总价格,总数量。。。
7 全选和反选
  1 全选 复选框绑定事件 change
  2 获取 data中的全选变量 allChecked
  3 直接取反 allChecked = !allChecked
  4 遍历购物车数组 让里面 商品 选中状态跟随 allChecked 改变而改变
  5 把购物车数组 和 allChecked 重新设置回data 把购物车 重新设置回缓存中
8 商品数量的编辑
  1 "+" "-" 按钮 绑定同一个点击事件 区分的关键 自定义属性
    1 "+" "+1"
    2 "-" "-1"
  2 传递被点击的商品id goods_id
  3 获取data中的购物车数组 来获取需要被修改的商品对象
  4 直接修改商品对象的数量 num
    当购物车的数量 =1 同时用户点击 "-"
    弹窗提示(showModal) 询问用户 是否要删除
    1 确定 直接执行删除
    2 取消 什么都不做
  5 把cart数组 重新设置回 缓存中 和 data中 this.setCart
9 点击结算
  1 判断有无收货地址
  2 判断有无商品信息
  3 以上都满足就跳转 支付页面

在这块代码中,我觉得最精妙的是对重新计算底部工具栏价格这一事件的封装,代码如下:

  // 设置购物车状态 同时重新计算底部工具栏的数据
  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 ? allChecked : false;
    this.setData({
      allChecked,
      totalPrice,
      totalNum,
      cart
    });
    wx.setStorageSync("cart", cart);
  },

将购物车重新写入缓存中,这样的话,不管是在商品详情页面点击加入购物车事件,还是在购物车页面进行全选、选择、添加、减少,抑或是在之后的结算页面计算价格并进行购物车删除,都直接调用此函数,十分方便,且思路清晰,在我今后的代码中,也会多采用封装函数的形式


个人主页

个人主页由于和订单相关,但由于我本人没有自己的企业号,且无法发送获取支付请求,因此只写了一个静态布局,其中主页是设计了逻辑的,在登陆前只显示登录按钮,登陆后显示头像以及头像的高斯模糊背景,这块代码在wxml中实现

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

布局

在跟着视频敲代码的过程中,我觉得收益颇深的就是老师对各个class的命名方式,非常的规范,如此一来,搭配less来设置样式,整体就十分的清晰,如下图

包括其中对wxml注释的应用,整体的代码写下来之后再回顾,我都能清楚的明白这块代码实现了什么功能,我要改这个样式应该到哪里去找,应该如何修改。

此处列出两个在学习的过程中印象很深的两个布局方法:

  1. 多用flex伸缩盒子布局,设置垂直水平的居中很方便,同时flex:n可以轻松的控制元素占比多少大小。padding和margin也很好的控制了元素的四周留空,体内留空,让界面看着特别舒服
  2. 如果想让容器固定在一个位置,其父容器的position属性须是relative,之后在选择该元素的position为absolute;

总结

尽管这次作业,我自己的想法和构思占比并不多,但由于大家都是初学,我认为打下基础是十分重要的,因此我把大量的时间放在了跟视频敲代码上,有些视频我看完之后,自己的效果并不能做到和视频中的一样,我会反复去看,去研究代码究竟是哪里出了问题。

而恰是在这一过程中,我学习并弄懂了好多函数的使用,比如说wx:for={{xxx}}是for语句循环,其中的item和key,index的含义了;bindtap="xxx",如何去js文件中定义xxx,如何用console.log()去找到自己需要获取的一大堆数据的信息;什么是回调地狱,promise函数应该如何去写,这样写的好处是避免代码看起来冗余复杂,诸如此类的知识还有好多,其中还是有好多我没完全弄懂的知识,但我相信在日后的学习中,这些问题也会慢慢揭开它们的面纱。

跟着敲代码,不断调试,这样对于我一个初学者去了解如何写好小程序的帮助是十分大的,之后的作业中我会加入自己的想法,自己去设计一个实用的小程序。



参考资料

https://www.bilibili.com/video/BV1nE41117BQ?t=121&p=110 bilibili黑马程序员_小程序教学视频

https://www.showdoc.com.cn/128719739414963?page_id=2513235043485226 教学视频中提供的接口文档

https://blog.csdn.net/iefreer/article/details/50775887 弹性内容布局方式

https://blog.csdn.net/weixin_38289699/article/details/78964143 弹性布局,flex:n的含义

https://www.cnblogs.com/goloving/p/9275776.html 绝对定位需要注意的点

https://www.cnblogs.com/luxiaoxing/p/7544375.html vw、vh究竟是什么意思

https://blog.csdn.net/qq_21602341/article/details/87820778 回调地狱的的解决方法,promise ,es7 async

posted @ 2020-10-20 10:38  _山水之间  阅读(306)  评论(0编辑  收藏  举报