vue - 练手项目:简易商城

学习vue的第一个练手的小项目在这里插入图片描述使用的是vue3.0

第一步:划分目录结构

在这里插入图片描述

第二步:引入两个css文件

将两个文件放在src->assets->css下
第一个css文件是normalize.css,让不同浏览器在渲染网页元素时形式更加统一,可以在github上下载。
第二个css文件是base.css文件,引入一些基础的样式

/* 引入css文件 */
@import "./normalize.css";

*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
  user-select: none;
  /* 禁止用户鼠标在页面上选中文字/图片等 */
  -webkit-tap-highlight-color: transparent;
  /* webkit是苹果浏览器引擎,tap点击,highlight背景高亮,color颜色,颜色用数值调节 */
  background: var(--color-background);
  color: var(--color-text);
  width: 100vw;
}

a {
  color: var(--color-text);
  text-decoration: none;
}


.clear-fix::after {
  clear: both;
  content: '';
  display: block;
  width: 0;
  height: 0;
  visibility: hidden;
}

.clear-fix {
  zoom: 1;
}

.arrow-right {
  border-top: 1px solid #999;
  border-left: 1px solid #999;
  width: 9px;
  height: 9px;
  background-color: transparent;
  transform: rotate(135deg);
  display: inline-block;
  margin-left: .1rem;
}

.left {
  float: left;
}

.right {
  float: right;
}

第三步:起别名

起别名可以更加方便,不用担心路径错误
建立vue.config.js文件,这里的vue.config.js文件里的配置会和其他配置自动合并

module.exports = {
  configureWebpack: {
    resolve: {
      alias: {
        'assets': '@/assets',
        'common': '@/common',
        'components': '@/components',
        'network': '@/network',
        'views': '@/views',
      }
    }
  },
}

vue的配置中,src文件的别名是@,在vue2.0中起别名不可以直接使用@,但是在vue3.0中可以直接使用。

第四步:商品数据

建立一个static文件夹,在文件夹下建立goodsList.json文件,在json文件中放入需要的商品数据

[{
    "image": "https://g-search3.alicdn.com/img/bao/uploaded/i4/i4/2204178125669/O1CN01ytAtEG1rkRtuCJocg_!!2204178125669.jpg_460x460Q90.jpg",
    "introduce": "幼儿园园服夏装小学生校服英伦风儿童班服演出服毕业照夏季套装定",
    "price": 108.00,
    "id": 1,
    "counter": 0
  },
  {
    "image": "https://g-search1.alicdn.com/img/bao/uploaded/i4/imgextra/i3/29490962/O1CN01pzgH1h1IydcPBx9Nm_!!0-saturn_solar.jpg_460x460Q90.jpg_.webp",
    "introduce": "欧美2019秋冬新款超高跟短靴女防水台粗跟马丁靴及裸靴单靴加绒潮",
    "price": 198.00,
    "id": 2,
    "counter": 0

  },
  {
    "image": "https://g-search2.alicdn.com/img/bao/uploaded/i4/i3/652176876/TB2z1KsXEFWMKJjSZFvXXaenFXa_!!652176876.jpg_460x460Q90.jpg_.webp",
    "introduce": "时装凉鞋女夏2020新款时尚性感一字带细跟极简网红百搭高跟鞋ins",
    "price": 399.00,
    "id": 3,
    "counter": 0

  },
  {
    "image": "https://g-search3.alicdn.com/img/bao/uploaded/i4/i2/617953624/O1CN011P2bbi1cdpvvloSQD_!!617953624.jpg_460x460Q90.jpg_.webp",
    "introduce": "2020新款t恤女夏短袖宽松韩范学生百搭上衣简约大码bf风休闲半袖",
    "price": 78.00,
    "id": 4,
    "counter": 0

  },
  {
    "image": "https://g-search1.alicdn.com/img/bao/uploaded/i4/imgextra/i3/738710194/O1CN012rS4Zb1DItQk5AKni_!!0-saturn_solar.jpg_460x460Q90.jpg_.webp",
    "introduce": "蓝牙耳机双耳头戴式跑步挂耳挂脖不入耳可插卡一体式骨传导耳麦",
    "price": 299.00,
    "id": 5,
    "counter": 0

  },
  {
    "image": "https://g-search3.alicdn.com/img/bao/uploaded/i4/i4/2201482912275/O1CN01PMAtgd1SfzeZ3V8W3_!!0-item_pic.jpg_460x460Q90.jpg_.webp",
    "introduce": "Type-c耳机适用小米8/9华为荣耀高音质入耳式耳塞语音线控带麦",
    "price": 59.00,
    "id": 6,
    "counter": 0

  },
  {
    "image": "https://g-search3.alicdn.com/img/bao/uploaded/i4/i3/835893472/O1CN01lyBYFJ1bWDk3UjIEo_!!0-item_pic.jpg_460x460Q90.jpg_.webp",
    "introduce": "骨传导蓝牙耳机不入耳无线跑步运动型双耳头戴挂耳挂脖式",
    "price": 399.00,
    "id": 7,
    "counter": 0

  },
  {
    "image": "https://g-search1.alicdn.com/img/bao/uploaded/i4/i4/38522192/O1CN014WSqOl1S3yk3s1IxK_!!38522192.jpg_460x460Q90.jpg_.webp",
    "introduce": "ESSONIO骨传导耳机不入耳跑步骑行运动挂耳式自带内存无线蓝牙",
    "price": 1789.00,
    "id": 8,
    "counter": 0

  }
]

在store下的index.js文件下引入json文件

import goodsList from "../../static/goodsList.json"

将商品数据存入state中

state: {
    goodsList,
  },

第五步:页面切换

商城一共有5个页面,首页、商品详情页、分类、购物车、我的。
我只写3个页面,首页、商品详情页、购物车
在这里插入图片描述先做页面切换,即底部的导航栏

  1. TabBar.vue文件,底部导航栏的最外层
<template>
  <div id="tab-bar">
    <slot></slot>
  </div>
</template>

<script>
export default {
  name: "Tabbar"
};
</script>

<style scoped>
#tab-bar {
  display: flex;
  background-color: #f6f6f6;

  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;

  box-shadow: 0 -3px 1px rgba(100, 100, 100, 0.1);
}
</style>
  1. TabBarItem.vue文件,封装底部导航栏中的小组件,例如我的、分类,但是这个组件是公共的一个组件,不存放具体信息
<template>
  <div class="tab-bar-item" @click="itemClick">
    <!-- 封装代码时,插槽会被对应的代码替换,所以最好将插槽包含在一个div中,在div中做各种事情 -->
    <div v-if="!isActive">
      <slot name="item-icon"></slot>
    </div>
    <div v-else>
      <slot name="item-icon-active"></slot>
    </div>
    <div :style="activeStyle">
      <slot name="item-text"></slot>
    </div>
  </div>
</template>

<script>
export default {
  name: "TabBarItem",
  props: {
    path: String,
    activeColor: {
      type: String,
      default: "#f00"
    }
  },
  computed: {
    isActive() {
      return this.$route.path.indexOf(this.path) !== -1;
    },
    // 动态绑定样式:文字颜色
    activeStyle() {
      return this.isActive ? { color: this.activeColor } : {};
    }
  },
  methods: {
    itemClick() {
      this.$router.replace(this.path);
    }
  }
};
</script>

<style scoped>
.tab-bar-item {
  flex: 1;
  text-align: center;
  /* 一般情况下,tabbar的高度都为49px */
  height: 49px;
  font-size: 14px;
}

.tab-bar-item img {
  width: 24px;
  height: 24px;
  margin-top: 3px;
  vertical-align: middle;
}
</style>
  1. MainTabBar.vue文件写具体的底部导航条信息
<template>
  <div>
    <tab-bar>
      <tab-bar-item path="/home">
        <img slot="item-icon" src="~assets/img/tabbar/home.png" />
        <img slot="item-icon-active" src="~assets/img/tabbar/home_active.png" />
        <div slot="item-text">首页</div>
      </tab-bar-item>
      <tab-bar-item path="/category">
        <img slot="item-icon" src="~assets/img/tabbar/category.png" />
        <img slot="item-icon-active" src="~assets/img/tabbar/category_active.png" />
        <div slot="item-text">分类</div>
      </tab-bar-item>
      <tab-bar-item path="/shopcart">
        <img slot="item-icon" src="~assets/img/tabbar/shopcart.png" />
        <img slot="item-icon-active" src="~assets/img/tabbar/shopcart_active.png" />
        <div slot="item-text">购物车</div>
      </tab-bar-item>
      <tab-bar-item path="/profile">
        <img slot="item-icon" src="~assets/img/tabbar/profile.png" />
        <img slot="item-icon-active" src="~assets/img/tabbar/profile_active.png" />
        <div slot="item-text">我的</div>
      </tab-bar-item>
    </tab-bar>
  </div>
</template>

<script>
// 引入tabbar组件
import TabBar from "components/common/tabbar/TabBar";
// 引入tabbaritem组件
import TabBarItem from "components/common/tabbar/TabBarItem";
export default {
  name: "MainTabBar",
  components: {
    TabBar,
    TabBarItem
  }
};
</script>

<style scoped>
</style>

这样写符合vue组件的思想,将每一部分都封装成一个独立的组件,方便复用也方便后期维护

  1. 建立views文件用于存放页面相关的组件在这里插入图片描述配置路由
const Home = () => import('../views/home/Home.vue')
const Category = () => import('../views/category/Category.vue')
const Shopcart = () => import('../views/shopcart/Shopcart.vue')
const Profile = () => import('../views/profile/Profile.vue')
const routes = [
  // 框架
  {
    path: '',
    redirect: '/home'
  },
  {
    path: '/home',
    component: Home
  },
  {
    path: '/category',
    component: Category
  },
  {
    path: '/shopcart',
    name: 'goShopItem',
    component: Shopcart
  },
  {
    path: '/profile',
    component: Profile
  }
]

为了网页上的路径更好看,加上mode: 'history'

const router = new VueRouter({
  routes,
  mode: 'history'
})
  1. 在App.vue页面注册使用组件
<template>
  <div id="app">
    <router-view></router-view>
    <main-tab-bar></main-tab-bar>
  </div>
</template>

<script>
import MainTabBar from "components/content/mainTabBar/MainTabBar";
export default {
  name: "app",
  components: {
    MainTabBar
  }
};
</script>

<style>
@import "assets/css/bace.css";
</style>

这样就可以实现页面之间的跳转了

第六步:顶部导航栏

将顶部导航栏封装成一个单独的组件,这样就可以在多个页面下直接使用了
导航条组成为左边、中间、右边,中间放导航栏信息,左右两边可以放一些按钮、返回键等在这里插入图片描述

<template>
  <div class="nav-bar">
    <!-- 在slot外面包一层div,用来定义样式,如果直接在slot上面定义样式会产生各种各样的问题 -->
    <div class="left">
      <slot name="left"></slot>
    </div>
    <div class="center">
      <slot name="center"></slot>
    </div>
    <div class="right">
      <slot name="right"></slot>
    </div>
  </div>
</template>

<script>
export default {
  name: "NavBar"
};
</script>

<style scoped>
.nav-bar {
  display: flex;
  /* 一般导航栏的高度都为44px,如果加状态栏的话为64px */
  height: 44px;
  line-height: 44px;
  text-align: center;
  box-shadow: 0 1px 1px rgba(100, 100, 100, 0.1);
}
.left,
.right {
  width: 100px;
}
.center {
  flex: 1;
}
</style>

这样导航条就做好了

第七步:首页

一. 在Home.vue组件中注册使用顶部导航条
import NavBar from "components/common/navbar/NavBar.vue";
components: {
    NavBar,
  }
<nav-bar class="nav-home">
   <div slot="center">首页</div>
</nav-bar>
二. 轮播图
  1. 在data中放入图片数据、计算器和定时器
data() {
    return {
      dataList: [
        "https://i1.mifile.cn/a4/xmad_15535933141925_ulkYv.jpg",
        "https://i1.mifile.cn/a4/xmad_15532384207972_iJXSx.jpg",
        "https://i1.mifile.cn/a4/xmad_15517939170939_oiXCK.jpg"
      ],
      currentIndex: 0, //默认显示图片
      timer: null //定时器
    };
  },
  1. 自动播放图片
<div class="item">
      <img :src="dataList[currentIndex]" />
</div>
mounted: function() {
    setTimeout(() => {
      this.runInv();
    });
  },
  methods: {
    runInv() {
      this.timer = setInterval(() => {
        this.gotoPage(this.nextIndex);
      }, 3000);
    }
  },
computed: {
    //下一张
    nextIndex() {
      if (this.currentIndex == this.dataList.length - 1) {
        return 0;
      } else {
        return this.currentIndex + 1;
      }
    }
  }
  1. 鼠标移入停止自动播放,鼠标移出继续自动播放
methods: {
    runInv() {
      this.timer = setInterval(() => {
        this.gotoPage(this.nextIndex);
      }, 3000);
    },
    stopInv() {
      clearInterval(this.timer);
    }
  },

在最外层的div

<div
    class="banner"
    @mouseover="stopInv()"
    @mouseout="runInv()"
    @touchstart="stopInv()"
    @touchend="runInv()"
  >
</div>
  1. 点击实现上一张下一张
methods: {
    gotoPage(index) {
      this.currentIndex = index;
    }
  },
  computed: {
    //上一张
    prevIndex() {
      if (this.currentIndex == 0) {
        return this.dataList.length - 1;
      } else {
        return this.currentIndex - 1;
      }
    },
    //下一张
    nextIndex() {
      if (this.currentIndex == this.dataList.length - 1) {
        return 0;
      } else {
        return this.currentIndex + 1;
      }
    }
  }
<div class="page" v-if="this.dataList.length > 1">
      <ul>
        <li @click="gotoPage(prevIndex)">&lt;</li>
        <li
          v-for="(item,index) in dataList"
          @click="gotoPage(index)"
          :class="{'current':currentIndex == index}"
          :key="index"
        >{{index+1}}</li>
        <li @click="gotoPage(nextIndex)">&gt;</li>
      </ul>
 </div>
  1. 完整代码
<template>
  <div
    class="banner"
    @mouseover="stopInv()"
    @mouseout="runInv()"
    @touchstart="stopInv()"
    @touchend="runInv()"
  >
    <div class="item">
      <img :src="dataList[currentIndex]" />
    </div>
    <div class="page" v-if="this.dataList.length > 1">
      <ul>
        <li @click="gotoPage(prevIndex)">&lt;</li>
        <li
          v-for="(item,index) in dataList"
          @click="gotoPage(index)"
          :class="{'current':currentIndex == index}"
          :key="index"
        >{{index+1}}</li>
        <li @click="gotoPage(nextIndex)">&gt;</li>
      </ul>
    </div>
  </div>
</template>

<script>
export default {
  name: "Swiper",
  data() {
    return {
      dataList: [
        "https://i1.mifile.cn/a4/xmad_15535933141925_ulkYv.jpg",
        "https://i1.mifile.cn/a4/xmad_15532384207972_iJXSx.jpg",
        "https://i1.mifile.cn/a4/xmad_15517939170939_oiXCK.jpg"
      ],
      currentIndex: 0, //默认显示图片
      timer: null //定时器
    };
  },
  mounted: function() {
    setTimeout(() => {
      this.runInv();
    });
  },
  methods: {
    gotoPage(index) {
      this.currentIndex = index;
    },
    runInv() {
      this.timer = setInterval(() => {
        this.gotoPage(this.nextIndex);
      }, 3000);
    },
    stopInv() {
      clearInterval(this.timer);
    }
  },
  computed: {
    //上一张
    prevIndex() {
      if (this.currentIndex == 0) {
        return this.dataList.length - 1;
      } else {
        return this.currentIndex - 1;
      }
    },
    //下一张
    nextIndex() {
      if (this.currentIndex == this.dataList.length - 1) {
        return 0;
      } else {
        return this.currentIndex + 1;
      }
    }
  }
};
</script>

<style scoped>
* {
  margin: 0;
  padding: 0;
}
ul li {
  list-style: none;
  float: left;
  width: 30px;
  height: 40px;
  line-height: 40px;
  text-align: center;
  cursor: pointer;
  color: rgba(255, 255, 255, 0.8);
  font-size: 14px;
}
.banner {
  max-width: 1260px;
  position: relative;
}
.banner img {
  width: 100%;
  display: block;
}
.banner .page {
  background: rgba(0, 0, 0, 0.5);
  position: absolute;
  right: 0;
  bottom: 0;
  width: 100%;
}
.banner .page ul {
  float: right;
}
.current {
  color: #ff6700;
}
</style>
  1. 在Home.vue页面注册使用
import Swiper from "components/common/swiper/Swiper.vue";
components: {
    Swiper
  }
<swiper></swiper>
三. 商品信息

在home文件夹下建立childHome文件夹,在childHome文件夹下创建Recommend.vue文件,用于展示商品信息。

引入json文件

import goodsList from "../../../../static/goodsList.json";
data() {
    return {
      goodsList
    };
  }

将商品信息展示在首页上

<div class="recommend" v-for="(item,index) in goodsList" :key="index">
        <router-link :to="{name:'Details', params: {goodId: item.id}}">
          <ul>
            <li>
              <img :src="item.image" />
            </li>
            <li class="introduce">{{item.introduce}}</li>
            <li class="price">¥{{item.price}}</li>
          </ul>
        </router-link>
      </div>

当点击商品时,通过router-link将商品的id传递到详情页

<router-link :to="{name:'Details', params: {goodId: item.id}}"></router-link>

全部代码

<template>
  <div class="total">
    <div class="hhh">
      <div class="recommend" v-for="(item,index) in goodsList" :key="index">
        <router-link :to="{name:'Details', params: {goodId: item.id}}">
          <ul>
            <li>
              <img :src="item.image" />
            </li>
            <li class="introduce">{{item.introduce}}</li>
            <li class="price">¥{{item.price}}</li>
          </ul>
        </router-link>
      </div>
    </div>
  </div>
</template>

<script>
import goodsList from "../../../../static/goodsList.json";
export default {
  name: "Recommend",
  data() {
    return {
      goodsList
    };
  }
};
</script>

<style scoped>
.total {
  display: flex;
}
.hhh {
  flex-grow: 1;
  margin-bottom: 64px;
}
.recommend {
  margin: 8px;
  display: inline-block;
}
li {
  list-style: none;
}
.recommend img {
  height: 300px;
  text-align: center;
}
.introduce {
  width: 300px;
  text-align: center;
  font-size: 14px;
  margin-top: 6px;
  margin-bottom: 6px;
}
.price {
  width: 300px;
  text-align: center;
  font-size: 18px;
  color: #f00;
}
</style>

第八步:详情页

一. 在详情页组件中注册使用顶部导航条

在这里插入图片描述使用左边的插槽插入返回按钮

  1. 引入组件
import NavBar from "components/common/navbar/NavBar.vue";
  1. 注册组件
components: {
    NavBar
  },
  1. 在页面上使用
<!-- 导航条 -->
    <div>
      <nav-bar class="nav-details">
        <div class="nav-left" slot="left" @click="goHome">
          <img src="~assets/img/detail/return.png" />
          返回
        </div>
        <div slot="center">详情页</div>
      </nav-bar>
    </div>
  1. 点击返回按钮返回首页
methods: {
	goHome() {
      this.$router.push("/home");
    },
}
二. 获取商品ID

通过首页Recomend.vue组件中传入的params,获取点击的商品的id值

data() {
    return {
      good_id: this.$route.params.goodId,
    };
  },
三. 展示商品

展示商品的图片、标题、价格在这里插入图片描述

<!-- 图片 -->
<div class="pic">
	<img :src="this.$store.state.goodsList[good_id - 1].image" />
</div>
<!-- 标题 -->
<div class="titles">
   <h3 slot="titles">{{ this.$store.state.goodsList[good_id - 1].introduce }}</h3>
</div>
<!-- 价格 -->
<div class="price">
  <h2 slot="price">¥{{ this.$store.state.goodsList[good_id - 1].price }}</h2>
</div>
四. 购买数量

在这里插入图片描述因为商品详情页中每个商品的购买数量需要传到购物车,所以这里使用的counter是json文件中的counter

<div>
	<button @click="add(good_id)">+</button>
	<span>
		购买数量:{{
			this.$store.state.goodsList[good_id - 1].counter
		}}
	</span>
	<button @click="sub(good_id)" :disabled="dis">-</button>
</div>

当点击+和-按钮时,购买数量不能少于0,当购买数量等于0时,应该将-按钮禁用,当大于0时,解除禁用

data() {
    return {
      dis: true
    };
  },
methods: {
    add(good_id) {
      this.$store.state.goodsList[good_id - 1].counter++;
      if (this.$store.state.goodsList[good_id - 1].counter >= 0) {
        this.dis = false;
      }
    },
    sub(good_id) {
      this.$store.state.goodsList[good_id - 1].counter--;
      if (this.$store.state.goodsList[good_id - 1].counter == 0) {
        this.dis = true;
      } else {
        this.dis = false;
      }
    },
}
五. 加入购物车

当商品数量大于0时,点击按钮,将商品加入到购物车里面
在这里插入图片描述如果商品已经加入购物车,再次点击按钮,显示请勿重复添加
在这里插入图片描述

<!-- 购物车 -->
<button
	class="stopcart"
	class="{ activeBgColor: isActive == true }"
	@click="addToCart"
>加入购物车</button>
data() {
    return {
      isActive: false,
    };
  },

在store的state中建立一个数组,专门存放购物车中的商品数据

state: {
    // 购物车
    cart: [],
  },

点击加入购物车,当cart中不存在此商品且商品数量大于0时,将商品信息添加到cart中,如果cart中已经存在此商品,不再添加,按钮变绿并且显示请勿重复添加

addToCart() {
      if (this.$store.state.goodsList[this.good_id - 1].counter >= 1) {
        this.isActive = true;
        //传入商品id
        const goodsListID = this.$store.state.goodsList[this.good_id - 1].id;
        const cart = this.$store.state.cart;
        const result = cart.find(ele => ele.id == goodsListID);
        if (result == undefined) {
          cart.push(this.$store.state.goodsList[this.good_id - 1]);
        } else {
          document.querySelector(".stopcart").innerHTML = "请勿重复添加";
        }
      }
    }
六. 详情页全部代码
<template>
  <div>
    <!-- 导航条 -->
    <div>
      <nav-bar class="nav-details">
        <div class="nav-left" slot="left" @click="goHome">
          <img src="~assets/img/detail/return.png" />
          返回
        </div>
        <div slot="center">详情页</div>
      </nav-bar>
    </div>
    <!-- 图片 -->
    <div class="pic">
      <img :src="this.$store.state.goodsList[good_id - 1].image" />
    </div>
    <!-- 文字 -->
    <div class="fonts">
      <!-- 标题 -->
      <div class="titles">
        <h3 slot="titles">{{ this.$store.state.goodsList[good_id - 1].introduce }}</h3>
      </div>
      <!-- 价格 -->
      <div class="price">
        <h2 slot="price">¥{{ this.$store.state.goodsList[good_id - 1].price }}</h2>
      </div>
      <!-- 购买数量 -->
      <div class="num">
        <div>
          <button @click="add(good_id)">+</button>
          <span>
            购买数量:{{
            this.$store.state.goodsList[good_id - 1].counter
            }}
          </span>
          <button @click="sub(good_id)" :disabled="dis">-</button>
        </div>
        <!-- 购物车 -->
        <button
          class="stopcart"
          :class="{ activeBgColor: isActive == true }"
          @click="addToCart"
        >加入购物车</button>
      </div>
    </div>
  </div>
</template>

<script>
import Vue from "vue";
import NavBar from "components/common/navbar/NavBar.vue";

export default {
  name: "Details",
  data() {
    return {
      isActive: false,
      good_id: this.$route.params.goodId,
      dis: true
    };
  },
  components: {
    NavBar
  },
  methods: {
    add(good_id) {
      this.$store.state.goodsList[good_id - 1].counter++;
      if (this.$store.state.goodsList[good_id - 1].counter >= 0) {
        this.dis = false;
      }
    },
    sub(good_id) {
      this.$store.state.goodsList[good_id - 1].counter--;
      if (this.$store.state.goodsList[good_id - 1].counter == 0) {
        this.dis = true;
      } else {
        this.dis = false;
      }
    },
    goHome() {
      this.$router.push("/home");
    },
    addToCart() {
      if (this.$store.state.goodsList[this.good_id - 1].counter >= 1) {
        this.isActive = true;
        //传入商品id
        const goodsListID = this.$store.state.goodsList[this.good_id - 1].id;
        const cart = this.$store.state.cart;
        const result = cart.find(ele => ele.id == goodsListID);
        if (result == undefined) {
          cart.push(this.$store.state.goodsList[this.good_id - 1]);
        } else {
          document.querySelector(".stopcart").innerHTML = "请勿重复添加";
        }
      }
    }
  }
};
</script>

<style scoped>
.nav-details {
  background-color: rgb(248, 94, 73);
  color: #fff;
}
.nav-details div img {
  width: 25px;
  position: relative;
  top: 6px;
}
.nav-left {
  margin-left: 10px;
}
.pic {
  text-align: center;
  margin-top: 20px;
}
.pic img {
  width: 300px;
  height: 300px;
}
.fonts {
  text-align: center;
  margin-top: 20px;
}

.price {
  color: #f00;
}
.num {
  margin-top: 6px;
  position: relative;
  /* margin-bottom: 100px; */
}
.num span {
  font-size: 20px;
  font-weight: 700;
  margin-left: 10px;
  margin-right: 10px;
}
button {
  width: 30px;
}
.stopcart {
  width: 150px;
  height: 30px;
  margin-top: 10px;
  color: #fff;
  background-color: rgb(248, 94, 73);
}
.activeBgColor {
  background-color: rgb(90, 138, 76);
}
</style>

第九步:购物车

将购物车一共封装为3个组件,购物车页面、购物车商品信息头部、购物车商品信息

一. 购物车页面

在这里插入图片描述1. 顶部导航条

import NavBar from "components/common/navbar/NavBar.vue";
  components: {
    NavBar
  },
    <!-- 顶部导航条 -->
    <nav-bar class="nav-home">
      <div class="nav-left" slot="left" @click="goHome">
        <img src="~assets/img/detail/return.png" />
        返回
      </div>
      <div slot="center">购物车</div>
    </nav-bar>
methods: {
    goHome() {
      this.$router.push("/home");
    }
  }
  1. 引入商品信息
import ShopHead from "../shopcart/childShopcart/ShopHead.vue";

components: {
    ShopHead
  },
<shop-head></shop-head>
  1. 全部代码
<template>
  <div>
    <!-- 顶部导航条 -->
    <nav-bar class="nav-home">
      <div class="nav-left" slot="left" @click="goHome">
        <img src="~assets/img/detail/return.png" />
        返回
      </div>
      <div slot="center">购物车</div>
    </nav-bar>
    <shop-head></shop-head>
  </div>
</template>

<script>
import NavBar from "components/common/navbar/NavBar.vue";
import ShopHead from "../shopcart/childShopcart/ShopHead.vue";

export default {
  name: "Shopcart",
  components: {
    NavBar,
    ShopHead
  },
  methods: {
    goHome() {
      this.$router.push("/home");
    }
  }
};
</script>

<style scoped>
.nav-home {
  background-color: rgb(248, 94, 73);
  color: #fff;
}
.nav-home div img {
  width: 25px;
  position: relative;
  top: 6px;
}
.nav-left {
  margin-left: 10px;
}
</style><template>
  <div>
    <!-- 顶部导航条 -->
    <nav-bar class="nav-home">
      <div class="nav-left" slot="left" @click="goHome">
        <img src="~assets/img/detail/return.png" />
        返回
      </div>
      <div slot="center">购物车</div>
    </nav-bar>
    <shop-head></shop-head>
  </div>
</template>

<script>
import NavBar from "components/common/navbar/NavBar.vue";
import ShopHead from "../shopcart/childShopcart/ShopHead.vue";

export default {
  name: "Shopcart",
  components: {
    NavBar,
    ShopHead
  },
  methods: {
    goHome() {
      this.$router.push("/home");
    }
  }
};
</script>

<style scoped>
.nav-home {
  background-color: rgb(248, 94, 73);
  color: #fff;
}
.nav-home div img {
  width: 25px;
  position: relative;
  top: 6px;
}
.nav-left {
  margin-left: 10px;
}
</style>
二. 购物车商品信息
  1. 获取store的state中的cart中的商品数据
data() {
    return {
      goodsList: this.$store.state.cart
    };
  },
  1. 展示信息
<tbody v-for="(item, index) in goodsList" :key="index">
      <tr>
        <td class="title">{{ item.introduce }}</td>
        <td>¥{{ item.price }}</td>
        <td>
          <button @click="sub(index)" :disabled="dis">-</button>
          <span>{{ item.counter }}</span>
          <button @click="add(index)">+</button>
        </td>
        <td>¥{{ item.counter * item.price }}</td>
        <td>
          <button class="delete" @click="detele(index)">删除</button>
        </td>
      </tr>
    </tbody>
  1. 购买数量
    在这里插入图片描述这个购买数量中的counter是获取json文件中的counter
    按钮的写法与详情页一致
    当商品数量等于0时,将商品在页面上删除
  2. 小计
    获取到json文件中的物品价格和物品数量,将他们相乘得到这个商品的价格小计
<td>¥{{ item.counter * item.price }}</td>
  1. 删除商品
    在这里插入图片描述点击按钮,删除商品的全部信息
<td>
	<button class="delete" @click="detele(index)">删除</button>
</td>
methods(){
	detele(index) {
      // arr.splice(下标,长度)
      this.goodsList.splice(index, 1);
    }
}
  1. 价格总计
    在这里插入图片描述将计算过程放在计算属性中
<div class="total">
      <div class="totalMoney">总计(不含运费)</div>
      <div class="money">
        已加入购物车商品
        <span>{{num}}件</span>,
        总价格:
        <span>¥{{money}}</span>
      </div>
    </div>
computed: {
    money() {
      return this.$store.state.cart
        .filter(item => {
          return item.id;
        })
        .reduce((preValue, item) => {
          return preValue + item.price * item.counter;
        }, 0);
    },
    num() {
      return this.$store.state.cart
        .filter(item => {
          return item.id;
        })
        .reduce((preValue, item) => {
          return preValue + item.counter;
        }, 0);
    }
  },
  1. 全部代码
<template>
  <div>
    <tbody v-for="(item, index) in goodsList" :key="index">
      <tr>
        <td class="title">{{ item.introduce }}</td>
        <td>¥{{ item.price }}</td>
        <td>
          <button @click="sub(index)" :disabled="dis">-</button>
          <span>{{ item.counter }}</span>
          <button @click="add(index)">+</button>
        </td>
        <td>¥{{ item.counter * item.price }}</td>
        <td>
          <button class="delete" @click="detele(index)">删除</button>
        </td>
      </tr>
    </tbody>
    <div class="total">
      <div class="totalMoney">总计(不含运费)</div>
      <div class="money">
        已加入购物车商品
        <span>{{num}}件</span>,
        总价格:
        <span>¥{{money}}</span>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "ShopItem",
  data() {
    return {
      goodsList: this.$store.state.cart,
      dis: false
    };
  },
  computed: {
    money() {
      return this.$store.state.cart
        .filter(item => {
          return item.id;
        })
        .reduce((preValue, item) => {
          return preValue + item.price * item.counter;
        }, 0);
    },
    num() {
      return this.$store.state.cart
        .filter(item => {
          return item.id;
        })
        .reduce((preValue, item) => {
          return preValue + item.counter;
        }, 0);
    }
  },
  methods: {
    add(index) {
      this.goodsList[index].counter++;
      if (this.goodsList[index].counter > 0) {
        this.dis = false;
      }
    },
    sub(index) {
      this.goodsList[index].counter--;
      if (this.goodsList[index].counter == 0) {
        this.goodsList.splice(index, 1);
      }
    },
    detele(index) {
      // arr.splice(下标,长度)
      this.goodsList.splice(index, 1);
    }
  }
};
</script>

<style scoped>
tbody {
  display: block;
  height: 88px;
  line-height: 88px;
  border-bottom: solid 1px #000;
  text-align: center;
}
.title {
  width: 500px;
}
td {
  width: 200px;
}
td button {
  width: 20px;
}
td span {
  margin-left: 10px;
  margin-right: 10px;
}
.delete {
  width: 60px;
}
.total {
  text-align: center;
  background-color: #eee;
  padding-top: 10px;
  padding-bottom: 20px;
}
.totalMoney {
  font-size: 30px;
}
.money {
  font-size: 20px;
  margin-top: 10px;
}
.money span {
  color: #f00;
}
</style>

三. 购物车商品信息头部

先写出头部信息

<table>
      <!-- 头部 -->
      <thead>
        <tr>
          <th class="title">商品名称</th>
          <th>商品单价</th>
          <th>购买数量</th>
          <th>小计</th>
          <th>商品操作</th>
        </tr>
      </thead>

引入点击加入的商品信息

import ShopItem from "../childShopcart/ShopItem.vue";
components: {
    ShopItem
  }
<shop-item></shop-item>

全部代码

<template>
  <div>
    <table>
      <!-- 头部 -->
      <thead>
        <tr>
          <th class="title">商品名称</th>
          <th>商品单价</th>
          <th>购买数量</th>
          <th>小计</th>
          <th>商品操作</th>
        </tr>
      </thead>
      <shop-item></shop-item>
    </table>
  </div>
</template>

<script>
import ShopItem from "../childShopcart/ShopItem.vue";
export default {
  name: "ShopHead",
  components: {
    ShopItem
  }
};
</script>

<style scoped>
table {
  margin-bottom: 74px;
}
/* 头部 */
thead {
  display: block;
  height: 44px;
  line-height: 44px;
  border-bottom: solid 1px #000;
}
/* .checkbox {
  width: 80px;
} */
.title {
  width: 500px;
}
th {
  width: 200px;
}
</style>

一个简易的商场就完成了

  • 33
    点赞
  • 268
    收藏
    觉得还不错? 一键收藏
  • 19
    评论
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值