1.工程目录
index.html
<!doctype html>
<html lang="en">
<head>
<title>购物商城</title>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</head>
<body>
<div id="app"></div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</body>
</html>
Nav.vue
<template>
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<!-- <a class="navbar-brand" href="#">电商网站实例</a> -->
<router-link class="navbar-brand" to="/">电商网站实例</router-link>
<!-- <a class="navbar-brand" href="#">加入购物车</a> -->
<router-link class="navbar-brand offset-10" to="/cart">
购物车
<!-- <span v-if="cartList.length">{{ cartList.length }}</span>
-->
<span
v-if="cartList.length"
class="badge badge-pill badge-danger">{{ cartList.length }}</span>
</router-link>
</nav>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "Nav",
computed: {
...mapState(["cartList"])
}
};
</script>
router的index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import List from '../views/List.vue'
import Cart from '../views/Cart.vue'
import Product from '../views/Product.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'List',
component: List
},
{
path:'/product/:id',
name:'Product',
component:Product,
props:true
},
{
path: '/cart',
name: 'Cart',
component:Cart
}
]
const router = new VueRouter({
routes,
// 去掉#
mode:'history'
})
export default router
store的index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
// 购物车列表
cartList: [],
// 商品列表
productList: [
{
id: 1,
name: 'AirPods',
brand: 'Apple',
image: require('../assets/1.jpg'),
sales: 10000,
cost: 1288,
color: '白色'
},
{
id: 2,
name: 'BeatsX 入耳式耳机',
brand: 'Beats',
image: require('../assets/2.jpg'),
sales: 11000,
cost: 1188,
color: '白色'
},
{
id: 3,
name: 'Beats Solo3 Wireless 头戴式式耳机',
brand: 'Beats',
image: require('../assets/3.jpg'),
sales: 5000,
cost: 2288,
color: '金色'
},
{
id: 4,
name: 'Beats Pill+ 便携式扬声器',
brand: 'Beats',
image: require('../assets/4.jpg'),
sales: 3000,
cost: 1888,
color: '红色'
},
{
id: 5,
name: 'Sonos PLAY:1 无线扬声器',
brand: 'Sonos',
image: require('../assets/5.jpg'),
sales: 8000,
cost: 1578,
color: '白色'
},
{
id: 6,
name: 'Powerbeats3 by Dr. Dre Wireless 入耳式耳机',
brand: 'Beats',
image: require('../assets/6.jpg'),
sales: 12000,
cost: 1488,
color: '金色'
},
{
id: 7,
name: 'Beats EP 头戴式耳机',
brand: 'Beats',
image: require('../assets/7.jpg'),
sales: 25000,
cost: 788,
color: '蓝色'
},
{
id: 8,
name: 'B&O PLAY BeoPlay A1 便携式蓝牙扬声器',
brand: 'B&O',
image: require('../assets/8.jpg'),
sales: 15000,
cost: 1898,
color: '金色'
},
{
id: 9,
name: 'Bose® QuietComfort® 35 无线耳机',
brand: 'Bose',
image: require('../assets/9.jpg'),
sales: 14000,
cost: 2878,
color: '蓝色'
},
{
id: 10,
name: 'B&O PLAY Beoplay H4 无线头戴式耳机',
brand: 'B&O',
image: require('../assets/10.jpg'),
sales: 9000,
cost: 2298,
color: '金色'
}
],
// 数组排重
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 getters = {
// 去掉重复的品牌
brands: state => {
const brands = state.productList.map(item => item.brand);
return state.getFilterArray(brands);
},
// 去掉重复的颜色
colors: state => {
const colors = state.productList.map(item => item.color);
return state.getFilterArray(colors);
}
}
const 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
})
}
},
// 修改商品数量
editCartCount (state, payload) {
const product = state.cartList.find(item => item.id === payload.id);
product.count += payload.count;
},
// 删除商品
deleteCart (state, id) {
state.cartList.splice(id, 1);
},
// 清空购物车
emptyCart (state) {
state.cartList = [];
}
}
export default new Vuex.Store({
state: state,
mutations: mutations,
actions: {
// 购买
buy (context) {
// 真实环境应通过 ajax 提交购买请求后再清空购物列表
return new Promise(resolve=> {
setTimeout(() => {
context.commit('emptyCart');
resolve();
}, 500)
});
}
},
modules: {
},
getters: getters
})
Cart.vue
<template>
<div class="container-fluid mt-3">
<h6>购物清单</h6>
<!-- 表格 -->
<table class="table table-striped">
<thead>
<tr>
<th>商品信息</th>
<th>单价</th>
<th>数量</th>
<th>小计</th>
<th>删除</th>
</tr>
</thead>
<tbody>
<tr v-if="!cartList.length">
<td class="text-center" colspan="5">购物车为空</td>
</tr>
<tr v-else v-for="(item, index) in cartList" :key="index">
<td>
<img :src="productList[item.id].image" alt height="50px" />
{{productList[item.id].name}}
</td>
<td>{{productList[item.id].cost}}</td>
<td>
<button @click="handleCount(index,1)" type="button" class="btn">+</button>
{{item.count}}
<button
@click="handleCount(index,-1)"
type="button"
class="btn"
>-</button>
</td>
<td>¥ {{productList[item.id].cost * item.count }}</td>
<td>
<!-- 删除按钮 -->
<button @click="deleteCart(index)" type="button" class="btn btn-danger">删除</button>
</td>
</tr>
</tbody>
</table>
<div v-show="cartList.length" class="float-right">
<div class="row px-5">
<h6>共计{{ countAll }}件商品</h6>
<h6>应付总额¥{{costAll}}</h6>
<button
@click="handleOrder()"
type="button" class="btn btn-primary mx-2">现在结算</button>
</div>
</div>
</div>
</template>
<script>
// 导入vuex
import { mapState, mapMutations } from "vuex";
export default {
name: "Cart",
methods: {
...mapMutations(["deleteCart", "editCartCount"," handleOrder"]),
// 计算count变化的方法
handleCount(index, count) {
if (count < 0 && this.cartList[index].count === 1) return;
this.$store.commit("editCartCount", {
id: this.cartList[index].id,
count: count
});
},
handleOrder () {
this.$store.dispatch('buy').then(() => {
window.alert('购买成功');
})
}
},
computed: {
...mapState(["cartList", "productList"]),
// 计算商品的数量
countAll() {
let count = 0;
this.cartList.forEach(item => {
count += item.count;
});
return count;
},
// 计算总金额
costAll() {
let cost = 0;
this.cartList.forEach(item => {
cost += this.productList[item.id].cost * item.count;
});
return cost;
}
}
};
</script>
List.vue
// List.vue
<template>
<div>
<!-- 列表 -->
<div class="mt-3">
<div>
<span>品牌:</span>
<!-- 按钮 -->
<button
v-for="(item, index) in brands"
:key="index"
:class="{on: item === filterBrand}"
type="button"
class="btn btn-outline-primary mx-2"
@click="handleFilterBrand(item)"
>{{item}}</button>
</div>
<div class="my-2">
<span>颜色:</span>
<!-- 按钮 -->
<button
v-for="(item, index) in colors"
:key="index"
type="button"
:class="{on: item === filterColor}"
class="btn btn-outline-primary mx-2"
@click="handleFilterColor(item)"
>{{item}}</button>
</div>
<div>
<span>排序:</span>
<!-- 按钮 -->
<button
:class="{on: order === ''}"
@click="handleOrderDefault"
type="button"
class="btn btn-outline-danger mx-2"
>默认</button>
<button
:class="{on: order === 'sales'}"
@click="handleOrderSales"
type="button"
class="btn btn-outline-danger mx-2"
>销量</button>
<button
:class="{on: order.indexOf('cost') > -1}"
@click="handleOrderCost"
type="button"
class="btn btn-outline-danger mx-2"
>价格</button>
</div>
</div>
<!-- 卡片 -->
<div class="row mt-4" >
<div v-for="(item, index) in filteredAndOrderedList" :key="index" class="col-lg-3">
<!-- <div class="card mt-3">
<img class="card-img-top" :src="item.image" alt />
<div class="card-body">
<h4 class="card-title">{{item.name}}</h4>
<h4 class="card-title">{{item.color}}</h4>
<p class="card-text">¥:{{item.cost}}</p>
</div>
</div>-->
<router-link class="card mt-3" :to="{name:'Product',params:{id:item.id}}">
<img class="card-img-top" :src="item.image" />
<div class="card-body">
<h4 class="card-title">{{item.name}}</h4>
<h4 class="card-title">{{item.color}}</h4>
<p class="card-text">¥:{{item.cost}}</p>
</div>
</router-link>
</div>
</div>
<div class="product-not-found" v-show="!filteredAndOrderedList.length">暂无相关商品</div>
</div>
</template>
<script>
// 导入vuex
import { mapState, mapGetters } from "vuex";
export default {
name: "List",
data() {
return {
// 过滤后的品牌
filterBrand: "",
// 过滤后的颜色
filterColor: "",
// 排序
order: ""
};
},
methods: {
// 过滤品牌的方法
handleFilterBrand(brand) {
if (this.filterBrand === brand) {
this.filterBrand = "";
} else {
this.filterBrand = brand;
}
},
// 过滤颜色的方法
handleFilterColor(color) {
if (this.filterColor === color) {
this.filterColor = "";
} else {
this.filterColor = color;
}
},
// 默认排序的方法
handleOrderDefault() {
this.order = "";
},
// 按照销量排序的方法
handleOrderSales() {
this.order = "sales";
},
// 按照价格排序的方法
handleOrderCost() {
if (this.order === "cost-desc") {
this.order = "cost-asc";
} else {
this.order = "cost-desc";
}
}
},
computed: {
// 映射
...mapState(["productList"]),
...mapGetters(["brands", "colors"]),
//根据条件计算过滤后的list
filteredAndOrderedList() {
let list = this.productList;
// 按品牌过滤
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;
}
}
};
</script>
Product.vue
// 商品详情
<template>
<!-- 卡片 -->
<div class="row mt-3">
<div class="col-lg-6">
<img :src="product.image" alt height="500px" width="500px"/>
</div>
<div class="col-lg-6">
<div class="row">
<div>
<h4>{{product.name}}</h4>
<p>¥:{{product.cost}}</p>
<span class="mr-3">尺码</span>
<!-- 按钮 -->
<button
v-for="(item, index) in sizeList"
:key="index"
type="button"
class="btn btn-outline-danger mx-1"
>{{item}}</button>
</div>
</div>
<div>
<div class="row mt-3">
<span class="mr-3">数量</span>
<div class="form-group">
<select class="form-control" name id>
<option v-for="(n, index) in 100" :key="index">{{n}}</option>
</select>
</div>
</div>
<div>
<div class="row">
<span class="mr-5">花呗分期</span>
<div class="row">
<!-- <button type="button" class="btn btn-primary">¥43.64x3期(包含手续费)</button> -->
<p>登录后确认是否享有该服务</p>
<a href>什么是花呗分期</a>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<button type="button" class="btn btn-outline-primary my-2">¥43.64x3期(包含手续费)</button>
<button type="button" class="btn btn-outline-primary">¥22.29x6期(包含手续费)</button>
</div>
<div class="col-lg-6 ">
<button type="button" class="btn btn-outline-primary my-2">¥11.46x12期(包含手续费)</button>
<button type="button" class="btn btn-outline-primary">¥5.046x24期(包含手续费)</button>
</div>
</div>
<!-- 加入购物车 -->
<div class="row mt-3">
<div class="col-lg-6 col-sm-12">
<button @click="buy()" type="button" name id class="btn btn-danger btn-lg btn-block">立即购买</button>
</div>
<div class="col-lg-6 col-sm-12">
<button
@click="handleAddToCart"
type="button"
name
id
class="btn btn-primary btn-lg btn-block"
>加入购物车</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "Product",
//
props: ["id"],
data() {
return {
product: "",
// 尺码
sizeList: ["M", "L", "XL", "2XL", "3XL"]
};
},
methods: {
// 过滤productList
getProduct() {
// console.log(this.id);
this.product = this.productList.find(item => item.id === this.id);
// console.log(this.product);
},
// 加入购物车
handleAddToCart() {
this.$store.commit("addCart", this.id);
},
// 立即购买的方法
buy(){
alert('购买成功,请返回')
}
},
computed: {
...mapState(["productList"])
},
//
mounted() {
this.getProduct();
}
};
</script>
App.vue
<template>
<div id="app">
<Nav/>
<!-- 容器 -->
<div class="container-fluid">
<router-view/>
</div>
</div>
</template>
<script>
// 导入
import Nav from './components/Nav'
export default {
name:'App',
components:{
Nav
}
}
</script>
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
命令行运行: