经典项目Vue饿了么外卖复盘

由于最近离职,趁着一段空白期,对过去做个一个比较详情的总结,也开始尝试写一写不一样的行业的项目,于是就开始在网上找了一些项目进行练手,做完之后其实收获还是挺多的,做个简单的心得分享和对项目的复盘,顺便复盘的时候还对项目整体做了一些优化,毕竟自己屎一样的代码真的难看,只能尽力做到让他看起来不那么像屎....


image.png

Vue饿了么项目网上流传的资料是1.0的版本,所以是完全照着设计稿写的,单纯做分享

先简单的来看下设计稿~


image.png

image.png

image.png

总览

商品首页,商家展示页,购物车详情,商品详情,评论页面,商家介绍页面~

整体来说有难度的点主要在于:
1,购物车功能
2.不同尺寸图标的组件业务逻辑需要进行适配
3.better-scroll的理解及运用

首先是通过Vue-cli搭建Vue项目环境,以及配置商品,评价,商家三个动态路由~

然后是一个比较重要的本地服务器搭建,因为数据是通过node服务器读取的,所以需要自己手动搭建一个node服务器,也比较简单

var express = require('express');
var app = express();
var cors = require('cors');
 
app.use(cors());
var appData = require('./data.json');

var seller = appData.seller;
var goods = appData.goods;
var ratings = appData.ratings;

app.get('/seller',function(req,res){
    res.json({
        code:202,
        data:seller
    })
});

app.get('/goods',function(req,res){
    res.json({
        code:202,
        data:goods
    })
});

app.get('/ratings',function(req,res){
    res.json({
        code:202,
        data:ratings
    })
});

app.listen(3000,function () {
    console.log('runing');
});

数据搞定~

1.按照顺序那就先来看看首页~

image.png

首页主要有的组件:
1.头部
2.底部购物车
3.商品路由下对应的商品组件

外加一个动态路由,头部不变,主要通过动态路由控制路由视图切换


头部组件

image.png

1.头部组件的布局,读取数据后进行相应的渲染
2.头部蒙层详情

-主要难点是在于如何根据后台返回的数据,渲染对应的蒙层icon图标
-如何根据评分渲染对应的星星评分

返回的json数据


image.png

后台根据不同的icon图标给了一个type字段,评分的话给了一个Number字段,表示当前的评分

icon图标怎么解决:

1.提前写好样式,把对应的icon类名提前声明在一个数组里,class类根据类名显示对应样式
2.supports[index]拿到的是当前index对应的type字段,通过点语法拿type字段对应的值
3.数组[下标] 拿到类名,最终就是需要展示的类名

 

//提前写好的类名放在数组
  data() {
    return {
     classMap:['subtract', 'discount', 'reduced' , 'ticket' ,'safeguard']
    };
//提前写好样式(展示部分)
// 減
  .subtract{
    background-image: url('../merchant/[email protected]');
  }
  //折扣
  .discount{
    background-image: url('../merchant/[email protected]');
  }
  //特惠
  .reduced{
    background-image: url('../merchant/[email protected]');
  }

根据评分显示对应的评分样式

需求:根据设置的尺寸大小,给定的星星分值渲染对应的星星
思路:
1.设置一个类为满星星
2.设置一个类为空星星
3.设置一个类为半星

根据类名进行星星的拼凑,星星需要接受父组件传来的两个参数,一个是星星的尺寸大小,一个是星星对应的Number字段

 
const num = 5; //滿星 const half = "half"; //半星 const on = "on"; //沒星 const off = "off"; export default { data() { return { num: 5 }; }, // 接收外部组件传过来的参数 props: ["size", "score"], //计算属性 computed: { //返回一个传进来的字符串与star进行拼接 star-24 star-36 sizes() { return "star-" + this.size; }, //返回一个数组,用来控制星星的生成,最终显示(item-star on) (item-star off) (item-star half),分别对应满星,无星,半星,数组排列的顺序,先是满星,然后是半星,最后是无星 arrs() { let arr = []; //全部的星星等于:整数星星加上半整星星加上空星星 // 把发过来的分数做处理,取0.5的倍数 let score = Math.floor(this.score * 2) / 2; // 如果这个分数除以1取余不等于0,说明不是整数 let has = score % 1 !== 0; //对发过来的数值做向下取整 let inte = Math.floor(score); //给所有整数星星加上on这个类 for (var i = 0; i < inte; i++) { arr.push(on); } //给所有半数星星加上half if (has) { arr.push(half); } //如果星星还没加满5个就给后面的全部补上 half for (var i = 0; i < this.num; i++) { if (arr.length < this.num) { arr.push(off); } } return arr; } } };

类名尺寸提前写好

image.png

头部差不多完成,接下来商品组件

商品组件的难点是在于左右联动,因为有了srcoll的加入,所以第一次做的时候会觉得有些难度,我是用的是better-scroll,scroll这里文档说的比较详情,我就不多说啦~

需求:
1.当左边侧栏滚动,右边商品对应到当前的商品类
2.当左边侧栏点击,右边商品对应到当前的商品类
3.当右边商品滚动,左边对应相应的商品类目,并高亮显示

思路(左边,右边index值对应):
1.声明高亮类名,判断当前栏目index是否等于计算属性传过来的值,为true,高亮的类名就会被添加上

  • {{item.name}}

  • 2.把右边的商品栏目的高度存入一个数组中,当左边的商品滚动的时候,通过循环去比较高度最后通过计算属性最终返回一个当前的index值(高度的初始化需要在Dom渲染完之后updated钩子函数)

    //在Dom元素渲染后才能拿到高度(updated里面,当然mounted也可以)
     indexHeight() {
                    //定义出一个li高度的数组
                    if (this.off) {
                        let hei = 0;
                        this.listheight.push(hei);
                        for (var i = 0; i < this.$refs.liheight.length; i++) {
                            let itme = this.$refs.liheight[i];
                            hei += itme.offsetHeight;
                            this.listheight.push(hei);
                        }
                        this.off = false;
                    }
                },
    //updated钩子函数中获取实时的y轴位置
     this.rightScroll.on("scroll", (pop) => {
                    //1.取绝对值
                    //2.取整
                    this.scrolly = Math.abs(Math.round(pop.y));
                });
    
              //左右联动,右边的数组高度判断,修改当前的index值,这个是在计算属性computed中
                indexsA() {
                    // 循环遍历有高度的那个数组
                    for (let i = 0; i < this.listheight.length; i++) {
                        //第一个高度
                        let hei1 = this.listheight[i];
                        //第二个高度
                        let hei2 = this.listheight[i + 1];
                        //如果是在第一个和第二个之间,说明正触发这个索引
                        //如果遍历到最后一个下标,那i+1就会报错,所以要加上!h2
                        if (!hei2 || this.scrolly >= hei1 && this.scrolly < hei2) {
                            return i;
                        }
                    }
                    return 0;
                },
    

    3.当用户点击左边的侧栏,通过better-scroll的Api实现

    // 左右联动,左边index值
                getindex(index) {
                    //获取右边滚动栏的元素
                    let foodList = this.$refs.liheight;
                    //通过index获取当前要滚动要的目标元素
                    let el = foodList[index];
                    this.rightScroll.scrollToElement(el, 300);
                },
    

    也可以稍微提一下关于watch和computed计算属性的一个区别

    watch监听

    watch是用来监听data里面的数据变化,也可以监听router路由,当监听的数据发生了变化,就会执行相应的监听函数

    computed计算属性

    computed是计算属性,每当computed里面依赖的data属性发生了变化,就会执行,最后返回一个值

    总结:左右联动的话,主要的代码就是这一些了~主要要注意的点:
    1.在什么生命钩子函数中执行相应的方法
    2.如何让左右两边共享一个index值,最后渲染高亮元素

    下面主要就聊一下小球动画还有购物车功能把,其他的话主要是一些样式和一些前面使用过的组件复用了

    小球动画

    image.png
      
    //列表需要用group包裹起来
    data() { return { //小球默认都是隐藏的,当用户点击一个,将数组中的小球显示 ball: [ {shows: false}, {shows: false}, {shows: false}, {shows: false}, {shows: false}, ], //存储显示的球,渲染的是这个数组 dropball:[], } }, //小球的逻辑 for (let i = 0; i < this.ball.length -1; i++) { //如果小球的状态是false if (!this.ball[i].shows) { //让小球的状态变成true this.ball[i].shows = true; //这是为turn的球 this.dropball.push(this.ball[i]); return; } } //小球动画开始前如果元素刚开始是没有创建的话,那这个beforeEnters是不会执行的 beforeEnters(el){ el.style.transform = 'translate(0,0)' }, enters(el,done){ let rf = el.offsetWidth; //当前点击元素的x, let balls = this.$refs.right.getBoundingClientRect(); //让当前小球的位置跟点击的元素位置一致 el.style.left = balls.left+'px'; el.style.top = balls.top+'px'; // //终点位置 let end= document.getElementById('icons').getBoundingClientRect(); // //0所在位置 let x = end.left - balls.left; let y = end.top - balls.top+50; el.style.transform =`translate(${x}px,${y}px)`; el.style.transition = 'all 0.7s linear'; //done(); }, //触发done的回调后让小球隐藏即可,但是这个有个BUG,done触发不了后续的动画(很蒙~),于是改用定时器, afterEnters(el){ var _this = this; let timid = setTimeout(function () { let ball = _this.dropball.shift(); //两个数组间的小球进行切换,把小球当前状态添加到false状态的小球中 _this.ball.push({ shows:false }); if (ball) { ball.show = false; el.style.display = 'none'; } clearInterval(timid); },700) },

    购物车

    image.png

    购物车功能主要是通过Vuex来实现,Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,通俗点来理解他就是一个所有组件都可以使用的一个数据中转枢纽,他里面常用的包含
    1.state,相当于全局组件都可以使用这个里面的数据
    2.Mutation,相当于全局组件的methods,所有组件都可以使用他的方法
    3.Getter,相当于全局组件的计算属性,每个组件都可以共享
    4,Action,主要处理异步代码

    安装完Vuex之后创建一个Vuex全局实例对象后就可以进行使用了,那就下来就是梳理需求
    1.需要一个全局都可以共享的当前购物数据
    2.通过当前的购物数据渲染不同的样式
    3.不同事件对Vuex属性中的state数据进行增删

    1.如何添加一个共享数据
    设计购物车所需要的数据

    this.obj是通过父组件传过来的当前点击的商品信息
    let obj = {
                  //名称
                 name:this.obj.name,
                  //id
                  id: this.obj.__ob__.dep.id,
                  //数量
                  num: 1,
                  //价格
                  price: this.obj.price,
                  //是否被选中
                  isok: true
                };
    
    //添加一个数据
    this.$store.commit('addcar', obj);
    

    vuex

     state: {
            car: [ ] ////将购物车中的数组,用一个数组存储起来
        },
    mutations: {
     addcar(state, obj) {
                //如果没有被选中
                var off = false;
                //假设这个商品已经被选中过
                for (let i = 0; i < state.car.length; i++) {
                    //在数组中通过id匹配到这个数组
                    if (state.car[i].id == obj.id) {
                        //把当前数组的num值加上穿过来的新num值
                        state.car[i].num ++;
                        off = true;
                    }
                }
                //如果这个商品没有被选中过,将这个商品加入数组中
                if (off == false) {
                    state.car.push(obj)
                }
            },
    }
    

    2.如何减少一个共享数据

    //减少的点击事件,把id传过去
     mins(id) {
                    this.$store.commit("minNum", {
                        id: id,
                        num: this.$refs.numbox.value
                    })
                },
    

    Vuex

    minNum(state, obj) {
                for (let i = 0; i < state.car.length; i++) {
                    //在数组中通过id匹配到这个数组
                    if (state.car[i].id == obj.id) {
                        //把当前数组的num值减1
                        if (state.car[i].num == 0){
                            state.car.splice(i,1);
                            return;
                        }else {
                            state.car[i].num --;
                        }
                    }
                }
            },
    

    3.如何调用Vuex的计算属性返回当前购物数据,进行渲染

    image.png

    这是一些需要用到的全局计算属性,不能一一展示,但是大致都是一样的,只要思路是正确的,都没什么问题
    Vuex

     getters: {
            //根据传过来的id返回当前id对应数组的num值
            getnum(state){
                var obj = {};
                for (let i = 0; i < state.car.length; i++) {
                    obj[state.car[i].id] = state.car[i].num;
                }
                return obj;
            },
            //计算所有num的和,还有总价
            getmax(state){
              var obj ={}
              var a =0;
              var str ='';
              var price = 0
                for (let i = 0; i < state.car.length; i++) {
                    price += state.car[i].num * state.car[i].price;
                    a += state.car[i].num;
                }
                obj.num = a;
                obj.price=price;
                if(obj.price<20){
                    str = '还差¥'+(20-obj.price)+'元起送'
                }else {
                    str='去结算'
                }
                obj.str =str;
                return obj;
            },
            // 样式控制的开关
            off(state){
                var obj = {};
                for (let i = 0; i < state.car.length; i++) {
                    obj[state.car[i].id] = state.car[i].num;
                }
                return obj;
            },
            // 获取当前购物车中的数据
            cardata(state){
                return state.car;
            }
        }
    

    大致购物车的主要代码就是这样了,只要渲染出一个样式,其实后面还是很好做的,代码还有很大的优化空间~ 优化中......

    你可能感兴趣的:(经典项目Vue饿了么外卖复盘)