Vue全新技术栈重构黄老师饿了么商家应用总结

4,823 阅读7分钟

项目仓库地址:github.com/konglingwen…

项目线上地址: http://123.57.204.48:5000

背景

自从学习了Vue后,能用Vue解决的场景用例最终我都尽可能的用Vue去实现。单纯的用例需求并没有完整的项目开发流程,从中能学到的东西也是有限的。在这之前除了使用Vue做过vue-music的移动端音乐播放器项目和vue-bytedanceJob(重构某独角兽互联网公司官方招聘网站)之外,自己并没有用Vue涉猎web端更复杂的业务场景。

为了找一个项目练习,我去github上开始了搜索,当看到https://github.com/ustbhuangyi/vue-sell这个项目时,感觉这个移动端应用的一些业务场景是自己没有接触过的,于是我就照着这个应用的UI功能用自己的知识体系技术栈进行了重构,大概不到半个月的时间,我完成了第一个commit提交到项目上线运行,本篇文章就从应用功能技术实现一些方面剖析此项目的开发过程以及采到的坑。

项目截图

项目技术栈

  1. 前端
    • vue开发项目核心框架
    • axios HTTP请求模块
    • lib-flexible 移动端屏幕适配方案
    • better-scroll 仿IOS效果的移动端滚动库
    • normalize.css 第三方css样式初始化模块
    • es 6/7 下一代javascript语法
  2. 后端
    • express 搭建服务端应用核心框架
  3. 开发
    • vue-cli 项目初始化脚手架
    • vue-devtools 项目开发环境调试工具
    • vscode chrome git macbookpro
  4. 部署

应用功能

  • 商品页

    • 商品分类导航和商品列表的联动效果
    • 点击商品分类菜单展示对应商品列表信息
    • 添加/删除商品到购物车
    • 点击商品进入到详情页面
    • 商品添加到购物车动画效果
    • 页面滚动到对应商品类别时的标题吸顶效果
  • 评论页

    • 综合评论信息渲染
    • 切换评论筛选项按钮展示对应的信息
    • 选择展示是否有内容的评论
  • 商家页

    • 商家店铺信息展示
    • 收藏店铺
    • 商家实景图片具有bounce效果的滑动显示
  • 应用头部

    • 点击展示详情
    • 公告信息动态滚动显示
  • 购物车

    • 根据商品个数显示不同的状态
    • 购物车商品列表
    • 支付弹窗
    • 清空购物车
    • 增加/删除商品
  • 应用局部优化

bounce效果是指在应用中页面位置滚动到一个端点继续滑动时出现反弹的效果,常见场景是IOS系统应用滑动效果

功能难点

商品导航和内容的左右联动效果

效果演示

完整的组件代码点github.com/konglingwen…

思路

由于商品导航和内容是两个独立的滚动容器,当滚动到一个目标内容块时怎么才能激活它所关联的导航项呢?我们知道导航项列表和内容列表在排列顺序上是一致的,如果能计算出内容滚动位置处在对应区间块的索引,也就得到了导航列表应该激活的目标索引,然后就可以用Vue数据驱动视图的思想去实现这一切。

容器的左右联动效果是指容器滚动到目标内容时激活其关联的导航菜单项并滚动到可视区域。

逻辑实现

找到要激活的目标导航项索引的第一步需要把商品内容的各个类别块在容器内的纵坐标位置存储起来(给之后找到激活的目标索引提供比较对象),由于列表内容时动态渲染的,所以这里需要等所有数据已经渲染完成后才能操作,下面直接看代码演示吧!

template部分

<template>
    /* 这里只显示部分代码*/
     <ul class="foods-list">
          <li ref="foodsGroup" class="foods-group" v-for="(item,index) in data" :key="index">
            <dl class="foods-group-wrapper">
              <dt :class="{fixed:currentIndex===index}" class="foods-group-name">{{item.name}}</dt>
              <dd
                class="foods-group-item"
                v-for="(food ,key) in item.foods"
                :key="key"
              >
                 {{food.name}}
              </dd>
            </dl>
          </li>
        </ul>
</template>

script部分

 
export default {
    data(){
      return {
          currentIndex: 0,//导航项激活的索引
          currentFood: {},
          data:[],
          sectionHeight: [0],//第一个高度块坐标`y`值为`0`
          // 渲染完成后的值为 `[0,1281,1459,1612,2000,2270,2565,2952,3574,4436]`
      }  
    },
    created() {
        request
          .get("/goods")
          .then(response => {
            this.data = response;
          })
          .then(() => {
            setTimeout(() => {
              const sections = this.$refs.foodsGroup;
    
              sections.reduce((prevTotal, current) => {
                const sectionHeight = prevTotal + current.clientHeight;
                this.sectionHeight.push(sectionHeight);
    
                return sectionHeight;
              }, 0);
            });
        });
  }
}

有了各个商品块的y坐标,下一步就需要注册容器元素的滚动事件了,在回调函数里通过找到实时滚动位置disanceY处在sectionHeight数组中两个相邻元素之间的位置从而就得到了待激活导航索引currentIndex的值,具体代码实现如下

<template>
    <div>
        <!--导航菜单-->
         <scroll class="menu">
            <ul class="menu-list">
              <li
                @tap="selectMenu(index)"
                class="menu-item"
                :class="{selected:currentIndex===index}"
                v-for="(item,index) in data"
                :key="index"
              >
               <span>{{ item.name}}</span>
              </li>
            </ul>
          </scroll>
          
        <!--商品内容-->
        <scroll ref="foodsScroll" @scroll="onFoodScroll" class="foods">
        <!--这里省略商品内容模板的代码-->
        </scroll>
    
    </div>
</template>

export default {

    // 这里省略其他代码
    
    methods:{
        onFoodScroll({ x, y }) {
          const distanceY = Math.abs(Math.round(y));
          for (let index = 0; index < this.sectionHeight.length; index++) {
            if (
              distanceY >= this.sectionHeight[index] &&
              distanceY < this.sectionHeight[index + 1]
            ) {
              this.currentIndex = index;
            }
          }
        }
    }
}

完整的组件代码点github.com/konglingwen…。由于左右两侧的布局容器都是基于better-scroll实现的页面滚动,所以这里需要侦听better-scroll提供的scroll事件而不是浏览器原生的滚动事件。查看better-scrollscroll事件API这里

添加/删除 商品到购物车

效果截图

完整代码github.com/konglingwen…

思路

添加商品到购物车是一个多场景的功能,由于这里的购物车功能是一个多页面联动的效果,购物车商品数量的实时更改也需要同步到商品内容页和商品详情页。从功能映射到javascript语言数据结构层面的话,不难想到对象引用传递的特点可以作为实现此功能的底层架构思路,那就让我们去实现它吧。

实现

为了统计商品的数量。首先需要给每一个商品信息对象添加一个默认值为0count属性,添加后的对象长这样

{
  "count": 0, // 此变量用来存储添加到购物车的数量
  "name": "皮蛋瘦肉粥",
  "price": 10,
  "oldPrice": "",
  "description": "咸粥",
  "sellCount": 229,
  "rating": 100,
  "info": "一碗皮蛋瘦肉粥,总是我到粥店时的不二之选。香浓软滑,饱腹暖心,皮蛋的Q弹与瘦肉的滑嫩伴着粥香溢于满口,让人喝这样的一碗粥也觉得心满意足",
  "ratings": [
    {
      "username": "3******b",
      "rateTime": 1469261964000,
      "rateType": 1,
      "text": "",
      "avatar": "http://static.galileo.xiaojukeji.com/static/tms/default_header.png"
    }
  ],
  "icon": "http://fuss10.elemecdn.com/c/cd/c12745ed8a5171e13b427dbc39401jpeg.jpeg?imageView2/1/w/114/h/114",
  "image": "http://fuss10.elemecdn.com/c/cd/c12745ed8a5171e13b427dbc39401jpeg.jpeg?imageView2/1/w/750/h/750"
}

由于每一个商品项都有一个添加到购物车的数量选择器功能,这样我们直接给商品数量选择器组件设计一个名为foodInfo的对象类型props,这样在增加/减少商品数量的时候直接操作foodInfocount属性来实现同步数据的效果。

/components/food-picker.vue 组件代码

<template>
  <div class="food-picker" @click.stop>
    <div class="reduce-wrapper" @click="reduce">
      <i class="iconfont reduce"></i>
    </div>
    <div class="counter">{{foodInfo.count}}</div>
    <div class="add-wrapper" @click="add">
      <i class="iconfont add"></i>
    </div>
  </div>
</template>
<script>
export default {
  name: "food-picker",
  props: {
    foodInfo: {
      type: Object,
      default: () => ({})
    }
  },
  methods: {
    reduce() {
      if (parseInt(this.foodInfo.count) > 0) {
        this.foodInfo.count--;
      }
    },
    add() {
      this.foodInfo.count++;
    }
  }
};
</script>
<style lang="less" scoped>
.food-picker {
  min-width: 180px;
  max-width: 200px;

  display: flex;
  align-items: center;
  width: 100%;
  justify-content: space-between;
  .iconfont {
    color: #00a0dc;
    font-size: 38px;
  }
  .counter {
    // margin: 0 20px;
  }
}
</style>

下一个目标

基于目前已经实现的功能,整个应用的数据都是以json文件的格式存储在服务器,服务端并没有可以用来增删改查API接口可供使用。下一步我计划做出管理后台和服务端API用来管理前端页面的数据,使所有模块的数据都是可配置的,这样前端所渲染出来的数据也都是动态的,能够整合三端到一个项目也满足了当下Web全栈开发的场景需要。

总结

通过真实的开发这样一个复杂交互的应用,自己对Vue在实际业务场景中的使用和理解有深入了一步。深入理解了Vue数据驱动视图改变的思想,熟练的掌握了组件化开发项目的流程,同时也感受到所带来的便利,为自己接下来预备做的中大型项目建筑好了桥梁。

支持

如果本项目对您学习有帮助,请您动手点个stargithub.com/konglingwen…。也希望您继续关注我的动态github.com/konglingwen…,有了您的支持我会有动力开源更多有趣的项目。

欢迎点赞和留言,谢谢!

本篇文章属于作者原创,转载参考请注明出处,谢谢!