Vue 简易商城开发

原文博客地址

藤原拓鞋的博客

开始

Vue是目前前端领域教火的框架,被广泛应用,插件和依赖包生态强大,诞生了许多大型应用

Vue 官网

学习了 vue 之后,利用所学知识搭建了个简易的商城,用到了 vue 开发中常用的技巧以及扩展包,通过这个小项目加深了对 vue 的学习
但因为没有做对应的后台,所以用到了浏览器本地储存作为临时仓库的模拟,以达到计算商品剩余量的目的

同样,对于登录和注册功能,也是通过 localStorage 暂存用户名与密码,所以只能暂时有一个用户~~

后台系统的话,以后我会用 node 或者 django 搭建出来的,这里先给出本前端项目地址:
https://github.com/li-car-fei/vue_shopping

注册与登录

在这里插入图片描述

如上所述,本项目暂用 localStorage 存储用户名和密码,以达到登录和注册功能

首先,我们先需要配置路由
npm i vue-router --save 安装包,然后在 index.js 中引入并且使用:

import VueRouter from "vue-router";
import Routers from "./router";

Vue.use(VueRouter);

//路由配置
const RouterConfig = {
  //使用H5 history模式
  mode: "history",
  routes: Routers,
};

const router = new VueRouter(RouterConfig);

//跳转前设置title
router.beforeEach((to, from, next) => {
  window.document.title = to.meta.title;
  next();
});
//跳转后设置scroll为原点
router.afterEach((to, from, next) => {
  window.scrollTo(0, 0);
});

记得最后一定要在 new Vue() 是添加 router 路由器:

const app = new Vue({
  el: "#app",
  router, // 添加router
  store, // 添加vuex
  //render代替app声明
  render: (h) => {
    return h(App);
  },
});

配置的路由,写在了 router.js 中,如下:

//meta中存有title,用作跳转路由时改变title标题
const routers = [
  {
    path: "/list",
    meta: {
      title: "商品列表",
    },
    component: (resolve) => require(["./views/list.vue"], resolve),
  },
  {
    path: "/product/:id",
    meta: {
      title: "商品详情",
    },
    component: (resolve) => require(["./views/product.vue"], resolve),
  },
  {
    path: "/cart",
    meta: {
      title: "购物车",
    },
    component: (resolve) => require(["./views/cart.vue"], resolve),
  },
  {
    path: "/login/:loginStatus",
    meta: {
      title: "登录注册",
    },
    component: (resolve) => require(["./views/login.vue"], resolve),
  },
  {
    path: "*",
    redirect: "/login/login", //重定向路由至登录页面,且传入的loginStatus参数是 login
  },
];
export default routers;

想了解vue-router源码解析,可看我另一篇博客:vue-router 源码解析

注意两点:

  • 可通过设置默认路由跳转至登陆界面,使用 redirect 属性即可
  • 可能会 index.js 有疑问,大多数项目可能是 main.js 作为入口文件的文件名,但这是基于 webpack 配置的项目,修改 webpack 入口配置即可,文件名无关紧要

关于登录界面,最关键的是在 created 生命周期准确地判断状态

  • 退出登录的状态
  • localStorage 有用户信息,已登录的状态
  • 未登录或注册的状态
created(){
            //获取路由中的传来的参数loginStatus
            if(this.$route.params.loginStatus === 'logout'){
                //如果是退出登录跳转过来,清空缓存
                window.localStorage.clear();
                //直接commit触发mutations,设置state的loginstatus为false
                this.$store.commit('getLoginStatus', false);
                return;
            }
            //获取state中登录状态
            const loginStatus = this.$store.state.loginStatus;
            if(loginStatus){
                //如果是已经登录的用户,url跳到list
                this.login = false;
                this.register = false;
                window.alert('您已经是登录状态')
                window.location.href = '/list'
            }
        }

关于登录注册界面的其他操作及参数,请自行查看,注释都较为清楚

商城主页

在这里插入图片描述

首先,我们介绍除了 router 以外,另一个全局共用的变量 vuex

vuex 可以将全局使用的变量进行统一管理,在 index.js 中 new Vue() 时进行注册即可,它可以帮组我们更好地管理全局共用的数据,防止出现多组件之间传变量,传方法的复杂应用

通过vuexdispatch,commit ,getters 机制,对状态进行统一管理,想了解vuex源码解析,可以看我的另一篇博客:vuex 源码解析

以下是 index.js 中配置的 vuex

//配置Vuex状态管理
const store = new Vuex.Store({
  state: {
    //商品列表信息
    productList: [],
    //购物车数据,数组形式,数据元素为对象(商品id,购买数量count)
    cartList: [],
    //当前用户账号
    username: window.localStorage.getItem("username"),
    //登录状态    !!转换为bool值
    loginStatus: !!window.localStorage.getItem("loginStatus"),
  },
  getters: {
    //返回品牌队列
    brands: (state) => {
      //获取所有product的brand并形成数组
      const brands = state.productList.map((item) => item.brand);
      //数组去重并返回
      return util.getFilterArray(brands);
    },
    //返回颜色队列
    colors: (state) => {
      //获取所有product的color并形成数组
      const colors = state.productList.map((item) => item.color);
      //数组去重并返回
      return util.getFilterArray(colors);
    },
  },
  //mutations更新state,更新视图
  mutations: {
    //初始化时,添加商品列表
    setProductList(state, data) {
      state.productList = data;
    },
    //添加商品到购物车
    addCart(state, obj) {
      const id = obj.id;
      //获取具体的product,以便修改其stocks库存
      let product = state.productList.find((item) => item.id === id);
      if (obj.inCart) {
        //此商品在购物车中,数量+1,对应库存-1
        let Added = state.cartList.find((item) => item.id === id);
        Added.count++;
        product.stocks--;
      } else {
        //此商品不在购物车中,加入到购物车中
        state.cartList.push({
          id: id,
          count: 1,
        });
        //对应库存-1
        product.stocks--;
      }
    },
    //修改购物车商品数量
    editCartCount(state, payload) {
      //浅拷贝,所以修改product中count可以直接修改carList,productList
      //先获取购物车与商品列表对应商品对象
      const product_L = state.productList.find(
        (item) => item.id === payload.id
      );
      const product_C = state.cartList.find((item) => item.id === payload.id);
      //修改数量与对应库存
      product_L.stocks -= payload.count;
      product_C.count += payload.count;
    },
    //删除某个购物车商品
    deleteCart(state, id) {
      //获取购物车中此商品的数量,用于归还库存
      const count = state.cartList.find((item) => item.id === id).count;
      //findIndex,用于切割购物车列表
      const index = state.cartList.findIndex((item) => item.id === id);
      //获取对应的商品在productList中的对象
      const product = state.productList.find((item) => item.id === id);
      //修改库存
      product.stocks += count;
      //修改购物车列表
      state.cartList.splice(index, 1);
    },
    //添加评论
    addComment(state, comment_obj) {
      const product = state.productList.find(
        (item) => item.id === comment_obj.product_id
      );
      console.log(product);
      const content = comment_obj.content;
      const username = state.username;
      product.comments.push({
        username: username,
        content: content,
      });
    },
    //清空购物车即购买,无需恢复库存
    emptyCart(state) {
      state.cartList = [];
    },
    //username是commit来的用户名,修改state中用户名
    getUser(state, username) {
      state.username = username;
    },
    //flag是commit过来的登录状态,修改state中的状态
    getLoginStatus(state, flag) {
      state.loginStatus = flag;
    },
  },
  actions: {
    //ajax请求商品列表,暂且使用setTimeout
    getProductList(context) {
      setTimeout(() => {
        //设置state.productList 为引入的product_data
        context.commit("setProductList", product_data);
      }, 150);
    },
    //购买
    buy(context) {
      //模拟ajax请求服务端响应后再清空购物车
      return new Promise((resolve) => {
        setTimeout(() => {
          context.commit("emptyCart");
          resolve("购买成功");
        }, 150);
      });
    },
    //添加商品到购物车
    add(context, id) {
      //首先查看登录状态,若未登录则跳转到登陆界面
      if (!context.state.loginStatus) {
        window.alert("请先登录,为您跳转到登录界面");
        //路由跳转
        window.location.href = "/login/login";
        return undefined;
      }
      //先查看购物车中是否有该商品
      const isAdded = context.state.cartList.find((item) => item.id === id);
      //查看对应商品的库存
      const stocks = context.state.productList.find((item) => item.id === id)
        .stocks;
      return new Promise((resolve, reject) => {
        if (stocks) {
          //有库存,修改数据
          //如果购物车中不存在商品,设置count为1,存在count++
          if (isAdded) {
            context.commit("addCart", {
              inCart: true,
              id: id,
            });
            resolve("加一");
          } else {
            context.commit("addCart", {
              inCart: false,
              id: id,
            });
            resolve("加入购物车");
          }
        } else {
          //无库存,返回信息
          reject("库存不足");
        }
      });
    },
    //修改购物车数量
    editCart(context, payload) {
      //先查看对应商品的库存
      const stocks = context.state.productList.find(
        (item) => item.id === payload.id
      ).stocks;
      return new Promise((resolve, reject) => {
        //如果是+1,根据库存返回结果与修改
        if (payload.count > 0) {
          if (stocks) {
            //有库存
            context.commit("editCartCount", payload);
            resolve("修改成功(+1)");
          } else {
            //无库存
            reject("库存不足");
          }
        } else {
          //如果是-1,直接修改即可
          resolve("修改成功(-1)");
          context.commit("editCartCount", payload);
        }
      });
    },
  },
});

同样的,要记得在 new Vue() 的时候添加配置

以上,我们可以看到项目的基本流程,包括添加购物车,清空购物车等等
可以看到在 add(context, id) 方法中,需要先判断登录状态,再进行库存的判断,是否加购的判断,最终返回一个 Promise 对象或者跳转路由
同样的,在 editCart(context, payload) 方法中,也是返回一个 Promise 对象,判断库存然后resolve()或者reject()
而在商品组件添加商品和在购物车页面中修改数量时,通过如下方式调用:

//添加购物车
handleAddCart(){
                //增加1件此商品至购物车,返回一个Promise
                this.$store.dispatch('add', this.id).then(text=>{
                    //console.log(text)
                },error=>{
                    window.alert(error)
                })
            },

//修改购物车数量,返回一个Promise
            handleCount(index, count){
                //判断是否只剩一件
                if(count < 0 && this.cartList[index].count === 1) return;
                this.$store.dispatch('editCart', {
                    id: this.cartList[index].id,
                    count: count
                }).then(text=>{
                    //console.log(text)
                },error=>{
                    window.alert(error)
                })
            },

同理,对于清空购物车的操作,也遵循上书方式

//清空购物车
handleOrder(){
                //由于dispatch buy action 返回一个promise对象,用then()调用
                this.$store.dispatch('buy').then(text => {
                    window.alert(text);
                })
            },

主页中,还有颜色,品牌的筛选,有销量和价格的排序,我认为自已的实现方法是较为笨重的,设置了filterBrand,filterColor作为筛选品牌和颜色的数组,order作为排序选项,然后通过两层 forEach 进行筛选,最终得到筛选,排序后的商品数组。希望大家有更好的实现方案能够私聊我或者直接在评论区分享~~
list.vue 定义:

export default {
  //使用组件product
  components: { Product },
  computed: {
    list() {
      //从Vuex获取商品列表信息
      return this.$store.state.productList;
    },
    brands() {
      //从getters中获取所有商品的品牌
      return this.$store.getters.brands;
    },
    colors() {
      return this.$store.getters.colors;
    },
    filteredAndOrderedList() {
      //拷贝原数组
      let list = [...this.list];
      //品牌过滤,分别forEach遍历productlist与品牌筛选队列,匹配的product暂存push
      if (this.filterBrand.length) {
        let list1 = [];
        list.forEach((item) => {
          this.filterBrand.forEach((brand) => {
            if (item.brand == brand) {
              list1.push(item);
            }
          });
        });
        list = list1;
      }
      //颜色过滤,分别forEach遍历productlist与颜色筛选队列,匹配的product暂存push
      if (this.filterColor.length) {
        let list1 = [];
        list.forEach((item) => {
          this.filterColor.forEach((color) => {
            if (item.color == color) {
              list1.push(item);
            }
          });
        });
        list = list1;
      }
      //排序
      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;
    },
  },
  data() {
    return {
      //品牌过滤选中的品牌
      filterBrand: [],
      //颜色过滤选中的颜色
      filterColor: [],
      //排序过滤选中的方式:
      //cost-desc价格降序
      //cost-asc价格升序
      //sales销量
      order: "",
    };
  },
  methods: {
    //品牌筛选
    handleFilterBrand(brand) {
      //点击品牌过滤,再次点击取消
      let index = this.filterBrand.findIndex((item) => item == brand);
      if (index > -1) {
        //若已经筛选的品牌包含此品牌,则删除
        this.filterBrand.splice(index, 1);
      } else {
        //将此品牌加入到品牌筛选队列中
        this.filterBrand.push(brand);
      }
    },
    //颜色筛选
    handleFilterColor(color) {
      //点击颜色过滤,再次点击取消
      let index = this.filterColor.findIndex((item) => item == color);
      if (index > -1) {
        //若已经筛选的颜色包含此颜色,则删除
        this.filterColor.splice(index, 1);
      } else {
        //将此颜色加入到颜色筛选队列中
        this.filterColor.push(color);
      }
    },
    handleOrderDefault() {
      this.order = "";
    },
    handleOrderSales() {
      this.order = "sales";
    },
    handleOrderCost() {
      //当点击升序时将箭头更新↓,降序设置为↑
      if (this.order === "cost-desc") {
        this.order = "cost-asc";
      } else {
        this.order = "cost-desc";
      }
    },
  },
  mounted() {
    //初始化时通过Vuex actions获取商品列表信息
    this.$store.dispatch("getProductList");
  },
};

购物车

在这里插入图片描述

关于购物车中,有总价的计算,商品数量的改变等,实现较为简单,此处不作赘述,看代码即可:

export default {
  name: "cart",
  data() {
    return {
      promotion: 0, //优惠金额
      promotionCode: "",
      productList: product_data,
    };
  },
  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;
    },
  },
  methods: {
    //通知Vuex,完成下单
    handleOrder() {
      //由于dispatch buy action 返回一个promise对象,用then()调用
      this.$store.dispatch("buy").then((text) => {
        window.alert(text);
      });
    },
    //验证优惠码,使用ccylovehs代替优惠券字符串
    handleCheckCode() {
      if (this.promotionCode === "") {
        window.alert("请输入优惠码");
        return;
      }
      if (this.promotionCode !== "ccylovehs") {
        window.alert("优惠码输入错误");
        return;
      }
      this.promotion = 500;
    },
    //修改购物车数量,返回一个Promise
    handleCount(index, count) {
      //判断是否只剩一件
      if (count < 0 && this.cartList[index].count === 1) return;
      this.$store
        .dispatch("editCart", {
          id: this.cartList[index].id,
          count: count,
        })
        .then(
          (text) => {
            //console.log(text)
          },
          (error) => {
            window.alert(error);
          }
        );
    },
    //根据index查找商品id进行删除
    handleDelete(index) {
      this.$store.commit("deleteCart", this.cartList[index].id);
    },
  },
};

商品详情页

商品详情页较之以上操作,多了评论的操作,如下:

export default {
  data() {
    return {
      //获取路由中的参数id
      id: parseInt(this.$route.params.id),
      product: null,
      comments: [],
      user_comment: "",
    };
  },
  methods: {
    getProduct() {
      setTimeout(() => {
        //模拟ajax获取具体商品数据
        this.product = this.$store.state.productList.find(
          (item) => item.id === this.id
        );
        this.comments = this.product.comments;
      }, 300);
    },
    handleAddCart() {
      //增加1件此商品至购物车,返回一个Promise
      this.$store.dispatch("add", this.id).then(
        (text) => {
          //console.log(text)
        },
        (error) => {
          window.alert(error);
        }
      );
    },
    comment() {
      const content = this.user_comment;
      const id = this.id;
      this.$store.commit("addComment", {
        product_id: id,
        content: content,
      });
      this.user_comment = "";
    },
  },
  mounted() {
    //初始化数据
    this.getProduct();
  },
};

结语

总体讲述了此商城项目,感谢阅读

感兴趣地可以到 github 上 clone 此项目:https://github.com/li-car-fei/vue_shopping

  • 7
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值