转载

VueCli+vuex+router+BootStrap实现购物商城部分

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')

命令行运行:

正文到此结束
本文目录