由于最近离职,趁着一段空白期,对过去做个一个比较详情的总结,也开始尝试写一写不一样的行业的项目,于是就开始在网上找了一些项目进行练手,做完之后其实收获还是挺多的,做个简单的心得分享和对项目的复盘,顺便复盘的时候还对项目整体做了一些优化,毕竟自己屎一样的代码真的难看,只能尽力做到让他看起来不那么像屎....
Vue饿了么项目网上流传的资料是1.0的版本,所以是完全照着设计稿写的,单纯做分享
先简单的来看下设计稿~
总览
商品首页,商家展示页,购物车详情,商品详情,评论页面,商家介绍页面~
整体来说有难度的点主要在于:
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.按照顺序那就先来看看首页~
首页主要有的组件:
1.头部
2.底部购物车
3.商品路由下对应的商品组件
外加一个动态路由,头部不变,主要通过动态路由控制路由视图切换
我是头部
商品
评价
商家
头部组件
1.头部组件的布局,读取数据后进行相应的渲染
2.头部蒙层详情
-主要难点是在于如何根据后台返回的数据,渲染对应的蒙层icon图标
-如何根据评分渲染对应的星星评分
返回的json数据
后台根据不同的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;
}
}
};
类名尺寸提前写好
头部差不多完成,接下来商品组件
商品组件的难点是在于左右联动,因为有了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值,最后渲染高亮元素
下面主要就聊一下小球动画还有购物车功能把,其他的话主要是一些样式和一些前面使用过的组件复用了
小球动画
//列表需要用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)
},
购物车
购物车功能主要是通过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的计算属性返回当前购物数据,进行渲染
这是一些需要用到的全局计算属性,不能一一展示,但是大致都是一样的,只要思路是正确的,都没什么问题
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;
}
}
大致购物车的主要代码就是这样了,只要渲染出一个样式,其实后面还是很好做的,代码还有很大的优化空间~ 优化中......