Vue电商网站项目开发总结
实现的功能:基于vue.js的知识点(webpack,Vuex,Vue-router)开发电商网站项目,实现的功能包括:商品列表按照价格、销量排序;商品列表按照品牌、价格过滤;动态的购物车,使用优惠码等。main.js:路由配置(router),使用History路由模式状态管理(Vuex,store),设置了state、getters、mutations、acti...
实现的功能:
基于vue.js的知识点(webpack,Vuex,Vue-router)开发电商网站项目,实现的功能包括:商品列表按照价格、销量排序;商品列表按照品牌、价格过滤;动态的购物车,使用优惠码等。
main.js:
路由配置(router),使用History路由模式
状态管理(Vuex,store),设置了state、getters、mutations、actions
router.js
路由的页面配置放在router.js文件内单独维护,设置了list(默认页面)、product/:id、cart的路由列表。
style.css
全局使用的CSS样式,在main.js中导入(import './style.css'),每个vue文件scoped的样式一同提取输出到main.css文件。
plugins:[
//重命名提取后的css文件
new ExtractTextPlugin({
filename:'[name].css',
allChunks:true
}),
//vue-loader在15.*后的版本需要伴生VueLoaderPlugin的,否则启动报错
new VueLoaderPlugin()
]
项目根目录下views目录下放置每个路由页面的.vue文件;components目录存放公共组件(product.vue定义了每个产品框的内容);images目录存放项目用到的照片。
1.模块拆分:
商品列表(list)用于展示相关的所有商品,具有筛选和排序两种过滤方法;筛选条件可以叠加,可以按照价格、销量等在筛选的基础上进行排序,最终筛选出符合要求的商品。
排序为单选,初始按“默认”进行排序,价格可分为升序和降序两种排序,销量按照降序;品牌和颜色为单选,单次点击选中,再次点击取消选中;
初次打开商品列表页请求一次远程数据(用setTimeout模拟异步,真实场景通过Ajax获取),获取到全部的商品数据,然后筛选和排序在本地完成。
商品列表主要有两个模块,路由组件(views/list.vue),负责数据的请求、过滤相关的逻辑;商品简介组件(components/produce.vue,每个商品卡片),鼠标划过时,显示“加入购物车”的按钮;两个模块的样式都直接写在各自vue文件的<style scoped>中。
2.商品简介组件:
每个商品的选项包括标题、价格、颜色等,为方便父子间传递,在product.vue中设置一个property:info来接收一个对象格式的数据,父级可直接将获取到的数据传递过来,省去拆分的工作。info数据结构如下:
product.js
{
id: 2,
name: 'BeatsX 入耳式耳机',
brand: 'Beats',
image: '../images/2.jpeg',
sales: 11000,
cost: 1188,
color: '白色'
}
颜色比较特殊,直接返回的中文无法对应到具体的色值,在product.vue的data选项中定义map,用于映射颜色和色值。
<router-link>最终会渲染成一个<a>标签,链接到:to定义的url(商品详情页),id作为参数通过vue-router传递。
components/product.vue
<router-link :to="'/product/' + info.id" class="product-main">
鼠标悬浮在卡片上时会显示“加入购物车”按钮的实现是绑定@click事件,使用prevent修饰符来阻止冒泡,否则在点击按钮的同时,也会点击到<a>标签进入详情页。
components/product.vue
<div class="product-add-cart" @click.prevent="handleCart">加入购物车</div>
“加入购物车”按钮先设置了handleCart方法,通过Vuex出发mutation保存到购物车,参数为商品的id。
components/product.vue
handleCart () {
this.$store.commit('addCart', this.info.id);
}
3.列表按照价格、销量排序(views/list.vue)
数据部分,列表相关的数据都通过Vuex来维护,获取商品列表的数据是异步获取的,写在Vuex的actions里。(真实场景中数据应当是通过ajax从服务端获取,实例是用setTimeout来模拟异步,并用本地数据来mock);数据位置是/product.js
main.js中导入数据,并在Vuex中声明数据列表相关的state、mutations、actions
通过action的getProductList方法获取数据(product_data对应于product.js中的数据),由mutation的setProduction方法将数据设置到productList
main.js
const store = new Vuex.Store({
state: {
productList: [],
cartList: []
},
mutations: {
// 添加商品列表
setProductList (state, data) {
state.productList = data;
}
}
actions: {
// 请求商品列表
getProductList (context) {
// 真实环境通过 ajax 获取,这里用异步模拟
setTimeout(() => {
context.commit('setProductList', product_data);
}, 500);
}
}
}
数据准备好之后,再关注视图部分,在根实例app.vue中挂载路由并设置导航条:
数据cartList是购物车中添加的商品;路由视图<router-view>挂载了所有的路由组件
app.vue
<template>
<div>
<div class="header">
<router-link to="/list" class="header-title">电商网站示例</router-link>
<div class="header-menu">
<router-link to="/cart" class="header-menu-cart">
购物车
<span v-if="cartList.length">{{ cartList.length }}</span>
</router-link>
</div>
</div>
<router-view></router-view>
</div>
</template>
<script>
export default {
computed: {
cartList () {
return this.$store.state.cartList;
}
}
}
</script>
商品列表list.vue在初始化时调用Vuex的action触发请求数据操作,并设置计算属性从Vuex中读取数据productList
list.vue
<Product v-for="item in list" :info="item" :key="item.id"></Product>
import Product from '../components/product.vue';
export default {
components: { Product },
computed: {
list () {
// 从Vuex获取商品列表数据
return this.$store.state.productList;
}
mounted () {
// 初始化时,通过Vuex的action请求数据
this.$store.dispatch('getProductList');
}
}
实现按照价格、销量排序,不能直接使用数据list,也不能直接重置list(过滤不是一次性的,不能破坏原数据,否则无法复原),所以用计算属性来动态返回过滤后的数据。
计算属性 filteredAndOrderedList将list进一步过滤,返回筛选、排序后的数据,排序依据于data:order;排序直接使用javas数组的sort方法对前后两个值比较大小;把<Production>循环的数据由list改为 filteredAndOrderedList 后,显示的就是过滤后的数据,之后在视图中通过操作改变order。
list.vue
<Product v-for="item in filteredAndOrderedList" :info="item" :key="item.id"></Product>
filteredAndOrderedList () {
// 复制原始数据
let list = [...this.list];
// 按品牌过滤
if (this.filterBrand !== '') {
list = list.filter(item => item.brand === this.filterBrand);
}
// 按颜色过滤
if (this.filterColor !== '') {
list = list.filter(item => item.color === this.filterColor);
}
// 排序
if (this.order !== '') {
if (this.order === 'sales') {
list = list.sort((a, b) => b.sales - a.sales);
} else if (this.order === 'cost-desc') {
list = list.sort((a, b) => b.cost - a.cost);
} else if (this.order === 'cost-asc') {
list = list.sort((a, b) => a.cost - b.cost);
}
}
return list;
}
需要在list.vue的模板里加入排序按钮,并绑定相关事件。“默认”和“销量”只能单词点击,“价格”按钮可以点击切换为升序和降序两种状态;通过判断order的状态,给3个按钮绑定class(.on)来高亮显示当前排序的按钮。
list.vue
<div class="list-control-order">
<span>排序:</span>
<span
class="list-control-order-item"
:class="{on: order === ''}"
@click="handleOrderDefault">默认</span>
<span
class="list-control-order-item"
:class="{on: order === 'sales'}"
@click="handleOrderSales">
销量
<template v-if="order === 'sales'">↓</template>
</span>
<span
class="list-control-order-item"
:class="{on: order.indexOf('cost') > -1}"
@click="handleOrderCost">
价格
<template v-if="order === 'cost-asc'">↑</template>
<template v-if="order === 'cost-desc'">↓</template>
</span>
</div>
4.列表按照品牌、颜色筛选
首先准备数据,品牌和颜色的数据可以作为getters从Vuex的productList里遍历获取。使用map方法把productList(product.js中数据)里的brand或color数据过滤出来,然后用getFilterArray方法对数组去重;getters里的brands和colors依赖数据productList,与计算属性原理类似,只要维护好productList,brands和colors就可以自动更新。
main.js
//数组去重
function getFilterArray (array) {
const res = [];
const json = {};
for (let i = 0; i < array.length; i++){
const _self = array[i];
if(!json[_self]){
res.push(_self);
json[_self] = 1;
}
}
return res;
}
// 数据过滤
const store = new Vuex.Store({
state: {
productList: [],
},
getters: {
brands: state => {
const brands = state.productList.map(item => item.brand);
return getFilterArray(brands);
},
colors: state => {
const colors = state.productList.map(item => item.color);
return getFilterArray(colors);
}
}
}
然后在list.vue中把Vuex里的品牌和颜色数据引入,完成列表的过滤;
list.vue
export default {
components: { Product },
computed: {
list () {
// 从Vuex获取商品列表数据
return this.$store.state.productList;
},
brands () {
return this.$store.getters.brands;
},
colors () {
return this.$store.getters.colors;
}
}
}
品牌和颜色都是单选(单次点击选中,再次点击取消选中),可以协同过滤。
5.商品详情页
views目录下新建product.vue文件作为“商品详情页”;商品详情的路由接收一个参数id,以id作为接口的索引,查询出所有相关的数据;为了使业务更好的解耦,从商品列表页跳转至详情页时,只传递商品的id,不需要其他数据,其他相信的数据重新获取;通过$route可以获取当前路由的参数,并在页面初始化是请求该商品的数据,示例使用setTimeout来模拟异步,真实场景下应该通过Ajax来请求数据。示例从数据源(product.js)里通过数组的find()方法拿到指定id的数据,完成数据mock。
views/product.vue
// 导入本地数据做匹配用,真实场景不需要
import product_data from '../product.js';
export default {
data () {
return {
// 获取当前路由中的参数
id: parseInt(this.$route.params.id),
product: null
}
},
methods: {
getProduct () {
// 真实环境通过 ajax 获取,这里用异步模拟
setTimeout(() => {
this.product = product_data.find(item => item.id === this.id);
}, 500);
},
// 加入购物车
handleAddToCart () {
this.$store.commit('addCart', this.id);
}
},
mounted () {
this.getProduct();
}
}
将数据写入模板,示例将10张产品的图片依次展示作为商品的内容。
6.购物车
购物车中需要完成结算,每件商品至少选择一件,可以删除,每件商品有价格小计;可以使用优惠码,使用后在总价的基础上减少500元;总价会根据购买商品的数量动态计算;右上角的 购物车入口也会显示当前购物车商品的数量。
商品加入购物车是通过Vuex来完成的,在main.js中,先定义Vuex中的state和mutations;数组cartList中保存购物车记录,数据格式为数组,包含商品id和购买数量两个数据(遵循解耦,其他信息通过id间接获取)。
main.js
const store = new Vuex.Store({
state: {
productList: [],
cartList: []
},
mutations: {
addCart (state, id) {
// 先判断购物车是否已有,如果有,数量+1
const isAdded = state.cartList.find(item => item.id === id);
if (isAdded) {
isAdded.count ++;
} else {
state.cartList.push({
id: id,
count: 1
})
}
}
}
}
购物车数据准备完毕,显示数据并动态修改数据;在app.vue中定义购物车入口和已添加商品数量(即list页面右上角购物车入口和显示数量);
views目录新建cart.vue文件,在router.js中添加购物车路由;在cart.vue中,准备动态数据:Vuex中的购物车数据cartList,product.js中所有商品的数据,将product.js中的数组转换为字典productDictList,方便快速选取,商品总数countAll,总费用costAll。这些数据都使用计算属性实现,productDictList是对象,key是商品id,value是商品信息,数据是product.js中每项的内容,通过id可以快速便捷的获取对应商品的信息。
cart.vue
computed: {
cartList () {
return this.$store.state.cartList;
},
productDictList () {
const dict = {};
this.productList.forEach(item => {
dict[item.id] = item;
});
return dict;
},
countAll () {
let count = 0;
this.cartList.forEach(item => {
count += item.count;
});
return count;
},
costAll () {
let cost = 0;
this.cartList.forEach(item => {
cost += this.productDictList[item.id].cost * item.count;
});
return cost;
}
下单前对每个商品的数量进行加减,或者删除商品,先将购物车cartList循环渲染,并完成表格样式。
handleCount方法用于修改购物车商品数量,最小为1;handleDelete方法用于删除商品;两者都根据接收的参数(循环cartList中的索引),并从数据cartList获取具体商品信息;两个方法通过$store.commit提交给Vuex的mutations来操作数据。
使用优惠码:优惠码的功能使用两个数据:promotionCode和promotion,前者用于双向绑定输入框数据,后者是优惠金额。优惠价默认是0,可以不再判断是否使用了优惠码。
cart.vue
handleCheckCode () {
if (this.promotionCode === '') {
window.alert('请输入优惠码');
return;
}
if (this.promotionCode !== 'Vue.js') {
window.alert('优惠码验证失败');
} else {
this.promotion = 500;
}
}
下单的操作通过Vuex的action完成,下单成功后,清空购物车数据;因为下单要通知服务端,所以需要在action中操作:
cart.vue
// 通知Vuex完成下单
handleOrder () {
this.$store.dispatch('buy').then(() => {
window.alert('购买成功');
})
}
main.js
mutations: {
// 清空购物车
emptyCart (state) {
state.cartList = [];
}
},
actions: {
// 购买
buy (context) {
// 真实环境应通过 ajax 提交购买请求后再清空购物列表
return new Promise(resolve=> {
setTimeout(() => {
context.commit('emptyCart');
resolve();
}, 500)
});
}
}
在action中,使用setTimeout模拟异步,并通过返回一个Promise对象来通知cart.vue的handleOrder购物完成。
项目代码Github地址:https://github.com/guocaiyao/VUE/tree/master/VuePractice/shopping
更多推荐
所有评论(0)