由于最近離職,趁著一段空白期,對(duì)過(guò)去做個(gè)一個(gè)比較詳情的總結(jié),也開(kāi)始嘗試寫(xiě)一寫(xiě)不一樣的行業(yè)的項(xiàng)目,于是就開(kāi)始在網(wǎng)上找了一些項(xiàng)目進(jìn)行練手,做完之后其實(shí)收獲還是挺多的,做個(gè)簡(jiǎn)單的心得分享和對(duì)項(xiàng)目的復(fù)盤(pán),順便復(fù)盤(pán)的時(shí)候還對(duì)項(xiàng)目整體做了一些優(yōu)化,畢竟自己屎一樣的代碼真的難看,只能盡力做到讓他看起來(lái)不那么像屎....
Vue餓了么項(xiàng)目網(wǎng)上流傳的資料是1.0的版本,所以是完全照著設(shè)計(jì)稿寫(xiě)的,單純做分享
先簡(jiǎn)單的來(lái)看下設(shè)計(jì)稿~
總覽
商品首頁(yè),商家展示頁(yè),購(gòu)物車詳情,商品詳情,評(píng)論頁(yè)面,商家介紹頁(yè)面~
整體來(lái)說(shuō)有難度的點(diǎn)主要在于:
1,購(gòu)物車功能
2.不同尺寸圖標(biāo)的組件業(yè)務(wù)邏輯需要進(jìn)行適配
3.better-scroll的理解及運(yùn)用
首先是通過(guò)Vue-cli搭建Vue項(xiàng)目環(huán)境,以及配置商品,評(píng)價(jià),商家三個(gè)動(dòng)態(tài)路由~
然后是一個(gè)比較重要的本地服務(wù)器搭建,因?yàn)閿?shù)據(jù)是通過(guò)node服務(wù)器讀取的,所以需要自己手動(dòng)搭建一個(gè)node服務(wù)器,也比較簡(jiǎn)單
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');
});
數(shù)據(jù)搞定~
1.按照順序那就先來(lái)看看首頁(yè)~
首頁(yè)主要有的組件:
1.頭部
2.底部購(gòu)物車
3.商品路由下對(duì)應(yīng)的商品組件
外加一個(gè)動(dòng)態(tài)路由,頭部不變,主要通過(guò)動(dòng)態(tài)路由控制路由視圖切換
<template>
<div id="app">
<headers>我是頭部</headers>
<div class="topnav">
<router-link to="/shopping" tag="div">商品</router-link>
<router-link to="/evaluate" tag="div">評(píng)價(jià)</router-link>
<router-link to="/merchant" tag="div">商家</router-link>
</div>
<router-view></router-view>
</div>
</template>
頭部組件
1.頭部組件的布局,讀取數(shù)據(jù)后進(jìn)行相應(yīng)的渲染
2.頭部蒙層詳情
-主要難點(diǎn)是在于如何根據(jù)后臺(tái)返回的數(shù)據(jù),渲染對(duì)應(yīng)的蒙層icon圖標(biāo)
-如何根據(jù)評(píng)分渲染對(duì)應(yīng)的星星評(píng)分
返回的json數(shù)據(jù)
后臺(tái)根據(jù)不同的icon圖標(biāo)給了一個(gè)type字段,評(píng)分的話給了一個(gè)Number字段,表示當(dāng)前的評(píng)分
icon圖標(biāo)怎么解決:
1.提前寫(xiě)好樣式,把對(duì)應(yīng)的icon類名提前聲明在一個(gè)數(shù)組里,class類根據(jù)類名顯示對(duì)應(yīng)樣式
2.supports[index]拿到的是當(dāng)前index對(duì)應(yīng)的type字段,通過(guò)點(diǎn)語(yǔ)法拿type字段對(duì)應(yīng)的值
3.數(shù)組[下標(biāo)] 拿到類名,最終就是需要展示的類名
<span class="icon-map" :class="classMap[datas.supports[index].type]"></span>
//提前寫(xiě)好的類名放在數(shù)組
data() {
return {
classMap:['subtract', 'discount', 'reduced' , 'ticket' ,'safeguard']
};
//提前寫(xiě)好樣式(展示部分)
// 減
.subtract{
background-image: url('../merchant/decrease_2@2x.png');
}
//折扣
.discount{
background-image: url('../merchant/discount_2@2x.png');
}
//特惠
.reduced{
background-image: url('../merchant/special_2@2x.png');
}
根據(jù)評(píng)分顯示對(duì)應(yīng)的評(píng)分樣式
需求:根據(jù)設(shè)置的尺寸大小,給定的星星分值渲染對(duì)應(yīng)的星星
思路:
1.設(shè)置一個(gè)類為滿星星
2.設(shè)置一個(gè)類為空星星
3.設(shè)置一個(gè)類為半星
根據(jù)類名進(jìn)行星星的拼湊,星星需要接受父組件傳來(lái)的兩個(gè)參數(shù),一個(gè)是星星的尺寸大小,一個(gè)是星星對(duì)應(yīng)的Number字段
<div class="star" :class="sizes">
<span v-for="(item,index) in arrs" :key="index" :class="item" class="item-star"></span>
</div>
const num = 5;
//滿星
const half = "half";
//半星
const on = "on";
//沒(méi)星
const off = "off";
export default {
data() {
return {
num: 5
};
},
// 接收外部組件傳過(guò)來(lái)的參數(shù)
props: ["size", "score"],
//計(jì)算屬性
computed: {
//返回一個(gè)傳進(jìn)來(lái)的字符串與star進(jìn)行拼接 star-24 star-36
sizes() {
return "star-" + this.size;
},
//返回一個(gè)數(shù)組,用來(lái)控制星星的生成,最終顯示(item-star on) (item-star off) (item-star half),分別對(duì)應(yīng)滿星,無(wú)星,半星,數(shù)組排列的順序,先是滿星,然后是半星,最后是無(wú)星
arrs() {
let arr = [];
//全部的星星等于:整數(shù)星星加上半整星星加上空星星
// 把發(fā)過(guò)來(lái)的分?jǐn)?shù)做處理,取0.5的倍數(shù)
let score = Math.floor(this.score * 2) / 2;
// 如果這個(gè)分?jǐn)?shù)除以1取余不等于0,說(shuō)明不是整數(shù)
let has = score % 1 !== 0;
//對(duì)發(fā)過(guò)來(lái)的數(shù)值做向下取整
let inte = Math.floor(score);
//給所有整數(shù)星星加上on這個(gè)類
for (var i = 0; i < inte; i++) {
arr.push(on);
}
//給所有半數(shù)星星加上half
if (has) {
arr.push(half);
}
//如果星星還沒(méi)加滿5個(gè)就給后面的全部補(bǔ)上 half
for (var i = 0; i < this.num; i++) {
if (arr.length < this.num) {
arr.push(off);
}
}
return arr;
}
}
};
</script>
類名尺寸提前寫(xiě)好
頭部差不多完成,接下來(lái)商品組件
商品組件的難點(diǎn)是在于左右聯(lián)動(dòng),因?yàn)橛辛藄rcoll的加入,所以第一次做的時(shí)候會(huì)覺(jué)得有些難度,我是用的是better-scroll,scroll這里文檔說(shuō)的比較詳情,我就不多說(shuō)啦~
需求:
1.當(dāng)左邊側(cè)欄滾動(dòng),右邊商品對(duì)應(yīng)到當(dāng)前的商品類
2.當(dāng)左邊側(cè)欄點(diǎn)擊,右邊商品對(duì)應(yīng)到當(dāng)前的商品類
3.當(dāng)右邊商品滾動(dòng),左邊對(duì)應(yīng)相應(yīng)的商品類目,并高亮顯示
思路(左邊,右邊index值對(duì)應(yīng)):
1.聲明高亮類名,判斷當(dāng)前欄目index是否等于計(jì)算屬性傳過(guò)來(lái)的值,為true,高亮的類名就會(huì)被添加上
<li
v-for="(item,index) in arr"
:key="index"
:class="{'current': index == indexsA}"
@click="getindex(index)">
<p>
<span v-if="item.type > 0" :class="classMap[item.type]" class="icons"></span>
{{item.name}}
</p>
</li>
2.把右邊的商品欄目的高度存入一個(gè)數(shù)組中,當(dāng)左邊的商品滾動(dòng)的時(shí)候,通過(guò)循環(huán)去比較高度最后通過(guò)計(jì)算屬性最終返回一個(gè)當(dāng)前的index值(高度的初始化需要在Dom渲染完之后updated鉤子函數(shù))
//在Dom元素渲染后才能拿到高度(updated里面,當(dāng)然mounted也可以)
indexHeight() {
//定義出一個(gè)li高度的數(shù)組
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鉤子函數(shù)中獲取實(shí)時(shí)的y軸位置
this.rightScroll.on("scroll", (pop) => {
//1.取絕對(duì)值
//2.取整
this.scrolly = Math.abs(Math.round(pop.y));
});
//左右聯(lián)動(dòng),右邊的數(shù)組高度判斷,修改當(dāng)前的index值,這個(gè)是在計(jì)算屬性computed中
indexsA() {
// 循環(huán)遍歷有高度的那個(gè)數(shù)組
for (let i = 0; i < this.listheight.length; i++) {
//第一個(gè)高度
let hei1 = this.listheight[i];
//第二個(gè)高度
let hei2 = this.listheight[i + 1];
//如果是在第一個(gè)和第二個(gè)之間,說(shuō)明正觸發(fā)這個(gè)索引
//如果遍歷到最后一個(gè)下標(biāo),那i+1就會(huì)報(bào)錯(cuò),所以要加上!h2
if (!hei2 || this.scrolly >= hei1 && this.scrolly < hei2) {
return i;
}
}
return 0;
},
3.當(dāng)用戶點(diǎn)擊左邊的側(cè)欄,通過(guò)better-scroll的Api實(shí)現(xiàn)
// 左右聯(lián)動(dòng),左邊index值
getindex(index) {
//獲取右邊滾動(dòng)欄的元素
let foodList = this.$refs.liheight;
//通過(guò)index獲取當(dāng)前要滾動(dòng)要的目標(biāo)元素
let el = foodList[index];
this.rightScroll.scrollToElement(el, 300);
},
也可以稍微提一下關(guān)于watch和computed計(jì)算屬性的一個(gè)區(qū)別
watch監(jiān)聽(tīng)
watch是用來(lái)監(jiān)聽(tīng)data里面的數(shù)據(jù)變化,也可以監(jiān)聽(tīng)router路由,當(dāng)監(jiān)聽(tīng)的數(shù)據(jù)發(fā)生了變化,就會(huì)執(zhí)行相應(yīng)的監(jiān)聽(tīng)函數(shù)
computed計(jì)算屬性
computed是計(jì)算屬性,每當(dāng)computed里面依賴的data屬性發(fā)生了變化,就會(huì)執(zhí)行,最后返回一個(gè)值
總結(jié):左右聯(lián)動(dòng)的話,主要的代碼就是這一些了~主要要注意的點(diǎn):
1.在什么生命鉤子函數(shù)中執(zhí)行相應(yīng)的方法
2.如何讓左右兩邊共享一個(gè)index值,最后渲染高亮元素
下面主要就聊一下小球動(dòng)畫(huà)還有購(gòu)物車功能把,其他的話主要是一些樣式和一些前面使用過(guò)的組件復(fù)用了
小球動(dòng)畫(huà)
<div class="ball-centent">
//列表需要用group包裹起來(lái)
<transition-group
@befor-enter="beforeEnters"
@enter="enters"
@after-enter="afterEnters"
name="ball"
tag="div">
<div v-for="(item,index) in dropball" :key="index" v-if="item.shows">
<div class="ball" v-if="balloff"></div>
</div>
</transition-group>
</div>
data() {
return {
//小球默認(rèn)都是隱藏的,當(dāng)用戶點(diǎn)擊一個(gè),將數(shù)組中的小球顯示
ball: [
{shows: false},
{shows: false},
{shows: false},
{shows: false},
{shows: false},
],
//存儲(chǔ)顯示的球,渲染的是這個(gè)數(shù)組
dropball:[],
}
},
//小球的邏輯
for (let i = 0; i < this.ball.length -1; i++) {
//如果小球的狀態(tài)是false
if (!this.ball[i].shows) {
//讓小球的狀態(tài)變成true
this.ball[i].shows = true;
//這是為turn的球
this.dropball.push(this.ball[i]);
return;
}
}
//小球動(dòng)畫(huà)開(kāi)始前如果元素剛開(kāi)始是沒(méi)有創(chuàng)建的話,那這個(gè)beforeEnters是不會(huì)執(zhí)行的
beforeEnters(el){
el.style.transform = 'translate(0,0)'
},
enters(el,done){
let rf = el.offsetWidth;
//當(dāng)前點(diǎn)擊元素的x,
let balls = this.$refs.right.getBoundingClientRect();
//讓當(dāng)前小球的位置跟點(diǎn)擊的元素位置一致
el.style.left = balls.left+'px';
el.style.top = balls.top+'px';
// //終點(diǎn)位置
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();
},
//觸發(fā)done的回調(diào)后讓小球隱藏即可,但是這個(gè)有個(gè)BUG,done觸發(fā)不了后續(xù)的動(dòng)畫(huà)(很蒙~),于是改用定時(shí)器,
afterEnters(el){
var _this = this;
let timid = setTimeout(function () {
let ball = _this.dropball.shift();
//兩個(gè)數(shù)組間的小球進(jìn)行切換,把小球當(dāng)前狀態(tài)添加到false狀態(tài)的小球中
_this.ball.push({
shows:false
});
if (ball) {
ball.show = false;
el.style.display = 'none';
}
clearInterval(timid);
},700)
},
購(gòu)物車
購(gòu)物車功能主要是通過(guò)Vuex來(lái)實(shí)現(xiàn),Vuex 是一個(gè)專為 Vue.js 應(yīng)用程序開(kāi)發(fā)的狀態(tài)管理模式,通俗點(diǎn)來(lái)理解他就是一個(gè)所有組件都可以使用的一個(gè)數(shù)據(jù)中轉(zhuǎn)樞紐,他里面常用的包含
1.state,相當(dāng)于全局組件都可以使用這個(gè)里面的數(shù)據(jù)
2.Mutation,相當(dāng)于全局組件的methods,所有組件都可以使用他的方法
3.Getter,相當(dāng)于全局組件的計(jì)算屬性,每個(gè)組件都可以共享
4,Action,主要處理異步代碼
安裝完Vuex之后創(chuàng)建一個(gè)Vuex全局實(shí)例對(duì)象后就可以進(jìn)行使用了,那就下來(lái)就是梳理需求
1.需要一個(gè)全局都可以共享的當(dāng)前購(gòu)物數(shù)據(jù)
2.通過(guò)當(dāng)前的購(gòu)物數(shù)據(jù)渲染不同的樣式
3.不同事件對(duì)Vuex屬性中的state數(shù)據(jù)進(jìn)行增刪
1.如何添加一個(gè)共享數(shù)據(jù)
設(shè)計(jì)購(gòu)物車所需要的數(shù)據(jù)
this.obj是通過(guò)父組件傳過(guò)來(lái)的當(dāng)前點(diǎn)擊的商品信息
let obj = {
//名稱
name:this.obj.name,
//id
id: this.obj.__ob__.dep.id,
//數(shù)量
num: 1,
//價(jià)格
price: this.obj.price,
//是否被選中
isok: true
};
//添加一個(gè)數(shù)據(jù)
this.$store.commit('addcar', obj);
vuex
state: {
car: [ ] ////將購(gòu)物車中的數(shù)組,用一個(gè)數(shù)組存儲(chǔ)起來(lái)
},
mutations: {
addcar(state, obj) {
//如果沒(méi)有被選中
var off = false;
//假設(shè)這個(gè)商品已經(jīng)被選中過(guò)
for (let i = 0; i < state.car.length; i++) {
//在數(shù)組中通過(guò)id匹配到這個(gè)數(shù)組
if (state.car[i].id == obj.id) {
//把當(dāng)前數(shù)組的num值加上穿過(guò)來(lái)的新num值
state.car[i].num ++;
off = true;
}
}
//如果這個(gè)商品沒(méi)有被選中過(guò),將這個(gè)商品加入數(shù)組中
if (off == false) {
state.car.push(obj)
}
},
}
2.如何減少一個(gè)共享數(shù)據(jù)
//減少的點(diǎn)擊事件,把id傳過(guò)去
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++) {
//在數(shù)組中通過(guò)id匹配到這個(gè)數(shù)組
if (state.car[i].id == obj.id) {
//把當(dāng)前數(shù)組的num值減1
if (state.car[i].num == 0){
state.car.splice(i,1);
return;
}else {
state.car[i].num --;
}
}
}
},
3.如何調(diào)用Vuex的計(jì)算屬性返回當(dāng)前購(gòu)物數(shù)據(jù),進(jìn)行渲染
這是一些需要用到的全局計(jì)算屬性,不能一一展示,但是大致都是一樣的,只要思路是正確的,都沒(méi)什么問(wèn)題
Vuex
getters: {
//根據(jù)傳過(guò)來(lái)的id返回當(dāng)前id對(duì)應(yīng)數(shù)組的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;
},
//計(jì)算所有num的和,還有總價(jià)
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='去結(jié)算'
}
obj.str =str;
return obj;
},
// 樣式控制的開(kāi)關(guān)
off(state){
var obj = {};
for (let i = 0; i < state.car.length; i++) {
obj[state.car[i].id] = state.car[i].num;
}
return obj;
},
// 獲取當(dāng)前購(gòu)物車中的數(shù)據(jù)
cardata(state){
return state.car;
}
}
大致購(gòu)物車的主要代碼就是這樣了,只要渲染出一個(gè)樣式,其實(shí)后面還是很好做的,代碼還有很大的優(yōu)化空間~ 優(yōu)化中......