如何使用 Vue 3 和 Vuex 构建购物车

作为Write for DOnations计划的一部分,作者选择了开源计划来接受捐赠

介绍

Vue.js是一个高性能的渐进式 Javascript框架。它是GitHub 上的一个流行框架,拥有一个活跃且乐于助人的社区。

为了展示 Vue Web 框架的功能,本教程将引导您构建电子商务应用程序的购物车。此应用程序将存储产品信息并保存客户想要购买的产品以供稍后结账。为了存储信息,您将探索一个广泛使用的 Vue.js 状态管理库:Vuex这将允许购物车应用程序将数据保存到服务器。您还将使用 Vuex处理异步任务管理。

完成本教程后,您将拥有一个功能正常的购物车应用程序,如下所示:

用户在购物车应用程序中添加和删除产品的动画

先决条件

步骤 1 — 使用 Vue CLI 设置应用程序

从 4.5.0 版本开始,Vue CLI现在提供了一个内置选项,用于在创建新项目时选择 Vue 3 预设。最新版本的 Vue CLI 允许您开箱即用地使用 Vue 3 并将您现有的 Vue 2 项目更新为 Vue 3。在这一步中,您将使用 Vue CLI 创建您的项目,然后安装前端依赖项.

首先,通过从终端执行以下命令来安装最新版本的 Vue CLI:

  • npm install -g @vue/cli

这将在您的系统上全局安装 Vue CLI。

注意:在某些系统上,全局安装 npm 包会导致权限错误,这会中断安装。由于避免使用sudowith是一种安全最佳实践npm install,因此您可以通过更改 npm 的默认目录来解决此问题。如果遇到EACCES错误,请按照官方 npm 文档中说明进行操作

使用以下命令检查您的版本是否正确:

  • vue --version

您将获得如下输出:

Output
@vue/cli 4.5.10

注意:如果您已经全局安装了旧版本的 Vue CLI,请从终端执行以下命令进行升级:

  • npm update -g @vue/cli

现在,您可以创建一个新项目:

  • vue create vuex-shopping-cart

这使用 Vue CLI 命令vue create创建一个名为vuex-shopping-cart. 有关 Vue CLI 的更多信息,请查看如何使用 Vue CLI 生成 Vue.js 单页应用程序

接下来,您将收到以下提示:

Output
Vue CLI v4.5.10 ? Please pick a preset: (Use arrow keys) ❯ Default ([Vue 2] babel, eslint) Default (Vue 3 Preview) ([Vue 3] babel, eslint) Manually select features

Manually select features从此列表中选择选项。

接下来,您将遇到以下提示以自定义您的 Vue 应用程序:

Output
... ◉ Choose Vue version ◯ Babel ◯ TypeScript ◯ Progressive Web App (PWA) Support ◉ Router ◉ Vuex ◯ CSS Pre-processors ◯ Linter / Formatter ❯◯ Unit Testing ◯ E2E Testing

从这个列表中,选择Choose Vue versionRouterVuex这将允许您选择您的 Vue 版本并使用 Vuex 和Vue Router

接下来,选择3.x (Preview)适合您的 Vue 版本,对 回答否 ( N) history mode,然后选择选项以进行配置In dedicated config file最后,回答N以避免为将来的项目保存设置。

此时,Vue 将创建您的应用程序。

创建项目后,使用以下命令进入文件夹:

  • cd vuex-shopping-cart

首先,您将安装 Bulma,这是一个基于Flexbox的免费开源 CSS 框架通过运行以下命令将 Bulma 添加到您的项目中:

  • npm install bulma

要在您的项目中使用 Bulma CSS,请打开您的应用程序的入口点,main.js文件:

  • nano src/main.js

然后添加以下突出显示的导入行:

vuex-shopping-cart/src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import './../node_modules/bulma/css/bulma.css'

createApp(App).use(store).use(router).mount('#app')

保存并关闭文件。

在这个应用程序中,您将使用Axios模块向您的服务器发出请求。通过运行以下命令添加 Axios 模块:

  • npm install axios

现在,运行应用程序以确保它正常工作:

  • npm run serve

http://localhost:8080在您选择的浏览器中导航到您将找到 Vue 应用程序欢迎页面:

在开发模式下运行应用程序时的默认 Vue 页面

一旦您确认 Vue 正在运行,请使用CTRL+C.

这一步,你在电脑中全局安装了Vue CLI,创建了一个Vue项目,安装了需要的npm包Axios和Bulma,并在main.js文件中将Bulma导入到项目中接下来,您将设置一个后端 API 来存储应用程序的数据。

第 2 步 – 设置后端

在这一步中,您将创建一个单独的后端来处理您的 Vue 项目。这将与您的前端 Vue 应用程序位于不同的项目文件夹中。

首先,移出你的 Vue 目录:

  • cd ..

创建一个名为 的单独目录cart-backend

  • mkdir cart-backend

拥有后端文件夹后,将其设为您的工作目录:

  • cd cart-backend

您将开始使用必要的文件初始化项目。使用以下命令创建应用程序的文件结构:

  • touch server.js
  • touch server-cart-data.json
  • touch server-product-data.json

您可以使用touch此处命令创建空文件。server.js文件将保存您的 Node.js 服务器,而JSON将保存商店产品和用户购物车的数据。

现在运行以下命令来创建一个package.json文件:

  • npm init

有关 npm 和 Node 的更多信息,请查看我们的How To Code in Node.js系列。

将这些后端依赖项安装到您的 Node 项目中:

  • npm install concurrently express body-parser

Express是一个用于 Web 应用程序的 Node 框架,它将为处理 API 请求提供有用的抽象。Concurrent将用于同时运行 Express 后端服务器和 Vue.js 开发服务器。最后,body-parser是一个 Express 中间件,它将解析对您的 API 的请求。

接下来,server.js在应用程序的根目录中打开一个文件:

  • nano server.js

然后添加以下代码:

购物车后端/server.js
const express = require('express');
const bodyParser = require('body-parser');
const fs = require('fs');
const path = require('path');

const app = express();
const PRODUCT_DATA_FILE = path.join(__dirname, 'server-product-data.json');
const CART_DATA_FILE = path.join(__dirname, 'server-cart-data.json');

app.set('port', (process.env.PORT || 3000));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use((req, res, next) => {
  res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
  res.setHeader('Pragma', 'no-cache');
  res.setHeader('Expires', '0');
  next();
});

app.listen(app.get('port'), () => {
  console.log(`Find the server at: http://localhost:${app.get('port')}/`);
});

此代码段首先将 Node 模块添加到您的后端,包括fs写入文件系统的path模块和使定义文件路径更容易模块。然后初始化 Expressapp并将对 JSON 文件的引用另存为PRODUCT_DATA_FILECART_DATA_FILE这些将用作数据存储库。最后,您创建了一个 Express 服务器,设置了端口,创建了一个中间件来设置响应头,并将服务器设置为侦听您的端口。有关 Express 的更多信息,请参阅Express 官方文档

setHeader方法设置 HTTP 响应的标头。在这种情况下,您使用Cache-Control来指导应用程序的缓存。有关这方面的更多信息,请查看Mozilla 开发人员网络关于 Cache-Control 的文章

接下来,您将创建一个 API 端点,您的前端将查询该端点以将商品添加到购物车。为此,您将使用app.post侦听 HTTPPOST请求。

server.js在最后一个app.use()中间件之后添加以下代码

购物车后端/server.js
...
app.use((req, res, next) => {
  res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
  res.setHeader('Pragma', 'no-cache');
  res.setHeader('Expires', '0');
  next();
});

app.post('/cart', (req, res) => {
    fs.readFile(CART_DATA_FILE, (err, data) => {
      const cartProducts = JSON.parse(data);
      const newCartProduct = { 
        id: req.body.id,
        title: req.body.title,
        description: req.body.description,
        price: req.body.price,
        image_tag: req.body.image_tag, 
        quantity: 1 
      };
      let cartProductExists = false;
      cartProducts.map((cartProduct) => {
        if (cartProduct.id === newCartProduct.id) {
          cartProduct.quantity++;
          cartProductExists = true;
        }
      });
      if (!cartProductExists) cartProducts.push(newCartProduct);
      fs.writeFile(CART_DATA_FILE, JSON.stringify(cartProducts, null, 4), () => {
        res.setHeader('Cache-Control', 'no-cache');
        res.json(cartProducts);
      });
    });
  });

app.listen(app.get('port'), () => {
  console.log(`Find the server at: http://localhost:${app.get('port')}/`);
});

此代码从前端接收包含购物车项目的请求对象,并将它们存储在server-cart-data.json项目根目录文件中。这里的产品是JavaScript的对象idtitledescriptionpriceimage_tag,和quantity属性。该代码还会检查购物车是否已经存在,以确保对重复产品的请求只会增加quantity.

现在,添加代码以创建 API 端点以从购物车中删除项目。这一次,您将使用app.delete来侦听 HTTPDELETE请求。

将以下代码添加到server.js上一个端点之后:

购物车后端/server.js
...
      fs.writeFile(CART_DATA_FILE, JSON.stringify(cartProducts, null, 4), () => {
        res.setHeader('Cache-Control', 'no-cache');
        res.json(cartProducts);
      });
    });
  });

app.delete('/cart/delete', (req, res) => {
  fs.readFile(CART_DATA_FILE, (err, data) => {
    let cartProducts = JSON.parse(data);
    cartProducts.map((cartProduct) => {
      if (cartProduct.id === req.body.id && cartProduct.quantity > 1) {
        cartProduct.quantity--;
      } else if (cartProduct.id === req.body.id && cartProduct.quantity === 1) {
        const cartIndexToRemove = cartProducts.findIndex(cartProduct => cartProduct.id === req.body.id);
        cartProducts.splice(cartIndexToRemove, 1);
      }
    });
    fs.writeFile(CART_DATA_FILE, JSON.stringify(cartProducts, null, 4), () => {
      res.setHeader('Cache-Control', 'no-cache');
      res.json(cartProducts);
    });
  });
});

app.listen(app.get('port'), () => {
  console.log(`Find the server at: http://localhost:${app.get('port')}/`); // eslint-disable-line no-console
});

此代码接收包含要从购物车中移除的商品的请求对象,并server-cart-data.json通过其id. 如果存在且数量大于 1,则扣除购物车中商品的数量。否则,如果商品的数量小于 1,它将从购物车中删除,其余商品将存储在server-cart-data.json文件中。

为了给您的用户提供额外的功能,您现在可以创建一个 API 端点来从购物车中删除所有项目。这也将侦听DELETE请求。

将以下突出显示的代码添加到server.js上一个端点之后:

购物车后端/server.js
...
    fs.writeFile(CART_DATA_FILE, JSON.stringify(cartProducts, null, 4), () => {
      res.setHeader('Cache-Control', 'no-cache');
      res.json(cartProducts);
    });
  });
});

app.delete('/cart/delete/all', (req, res) => {
  fs.readFile(CART_DATA_FILE, () => {
    let emptyCart = [];
    fs.writeFile(CART_DATA_FILE, JSON.stringify(emptyCart, null, 4), () => {
      res.json(emptyCart);
    });
  });
});

app.listen(app.get('port'), () => {
  console.log(`Find the server at: http://localhost:${app.get('port')}/`); // eslint-disable-line no-console
});

此代码负责通过返回一个空数组从购物车中删除所有项目

接下来,您将创建一个 API 端点以从产品存储中检索所有产品。这将用于app.get侦听GET请求。

将以下代码添加到server.js上一个端点之后:

购物车后端/server.js
...
app.delete('/cart/delete/all', (req, res) => {
  fs.readFile(CART_DATA_FILE, () => {
    let emptyCart = [];
    fs.writeFile(CART_DATA_FILE, JSON.stringify(emptyCart, null, 4), () => {
      res.json(emptyCart);
    });
  });
});

app.get('/products', (req, res) => {
  fs.readFile(PRODUCT_DATA_FILE, (err, data) => {
    res.setHeader('Cache-Control', 'no-cache');
    res.json(JSON.parse(data));
  });
});
...

此代码使用文件系统的本机readFile方法来获取文件中的所有数据server-product-data.json并以 JSON 格式返回它们。

最后,您将创建一个 API 端点以从购物车存储中检索所有项目:

购物车后端/server.js
...
app.get('/products', (req, res) => {
  fs.readFile(PRODUCT_DATA_FILE, (err, data) => {
    res.setHeader('Cache-Control', 'no-cache');
    res.json(JSON.parse(data));
  });
});

app.get('/cart', (req, res) => {
  fs.readFile(CART_DATA_FILE, (err, data) => {
    res.setHeader('Cache-Control', 'no-cache');
    res.json(JSON.parse(data));
  });
});
...

同样,此代码使用文件系统的本机readFile方法获取文件中的所有数据server-cart-data.json并以 JSON 格式返回它们。

保存并关闭server.js文件。

接下来,您将向 JSON 文件添加一些模拟数据以进行测试。

打开server-cart-data.json你之前创建文件:

  • nano server-cart-data.json

添加以下产品对象数组:

购物车后端/服务器购物车数据.json
[
    {
        "id": 2,
        "title": "MIKANO Engine",
        "description": "Lorem ipsum dolor sit amet, consectetur  dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis! ",
        "price": 650.9,
        "image_tag": "diesel-engine.png",
        "quantity": 1
    },
    {
        "id": 3,
        "title": "SEFANG Engine",
        "description": "Lorem ipsum dolor sit amet, consectetur  dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis!",
        "price": 619.9,
        "image_tag": "sefang-engine.png",
        "quantity": 1
    }
]

这显示了将在用户的购物车中启动的两个引擎。

保存并关闭文件。

现在打开server-product-data.json文件:

  • nano server-product-data.json

server-product-data.json文件中添加以下数据

购物车后端/服务器产品数据.json
[
    {
      "id": 1,
      "title": "CAT Engine",
      "description": "Lorem ipsum dolor sit amet, consectetur  dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis!",
      "product_type": "power set/diesel engine",
      "image_tag": "CAT-engine.png",
      "created_at": 2020,
      "owner": "Colton",
      "owner_photo": "image-colton.jpg",
      "email": "[email protected]",
      "price": 719.9
    },
    {
      "id": 2,
      "title": "MIKANO Engine",
      "description": "Lorem ipsum dolor sit amet, consectetur  dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis! ",
      "product_type": "power set/diesel engine",
      "image_tag": "diesel-engine.png",
      "created_at": 2020,
      "owner": "Colton",
      "owner_photo": "image-colton.jpg",
      "email": "[email protected]",
      "price": 650.9
    },
    {
      "id": 3,
      "title": "SEFANG Engine",
      "description": "Lorem ipsum dolor sit amet, consectetur  dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis!",
      "product_type": "power set/diesel engine",
      "image_tag": "sefang-engine.png",
      "created_at": 2017,
      "owner": "Anne",
      "owner_photo": "image-anne.jpg",
      "email": "[email protected]",
      "price": 619.9
    },
    {
      "id": 4,
      "title": "CAT Engine",
      "description": "Lorem ipsum dolor sit amet, consectetur  dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis!",
      "product_type": "power set/diesel engine",
      "image_tag": "lawn-mower.png",
      "created_at": 2017,
      "owner": "Irene",
      "owner_photo": "image-irene.jpg",
      "email": "[email protected]",
      "price": 319.9
    }

  ]

这将包含用户可以放入购物车的所有可能产品。

保存并关闭文件。

最后,执行这个命令来运行服务器:

  • node server

您将在终端上收到类似的信息:

Output
Find the server at: http://localhost:3000/

让此服务器在此窗口中运行。

最后,您将在 Vue 应用程序中设置代理服务器。这将启用前端和后端之间的连接。

转到您的 Vue 应用程序的根目录:

  • cd ../vuex-shopping-cart

在终端中,运行此命令以创建 Vue 配置文件:

  • nano vue.config.js

然后,添加以下代码:

vuex-shopping-cart/vue.config.js
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000/',
        changeOrigin: true,
        pathRewrite: {
          '^/api': ''
        }
      }
    }
  }
}

这会将请求从您的前端发送到您的后端服务器http://localhost:3000/有关代理配置的更多信息,请查看VuedevServer.proxy文档

保存并关闭文件。

在此步骤中,您编写了服务器端代码,用于处理购物车的 API 端点。您从创建文件结构开始,最后在文件中添加必要的代码server.js并在 JSON 文件中添加数据。接下来,您将为前端设置状态存储。

第 3 步 — 使用 Vuex 设置状态管理

在 Vuex 中,store 是保存应用程序状态的地方。应用程序状态只能通过在组件内分派动作来更新,然后会触发存储中的突变。Vuex 存储由状态、突变、动作和 getter 组成。

在这一步中,您将构建这些部分中的每一个,之后您将把所有内容耦合到一个 Vuex 存储中。

状态

现在您将为您的应用程序创建一个存储状态的地方。

项目store根目录src文件夹是在项目设置时自动创建的。storesrc项目目录中找到该文件夹,然后创建一个名为 的新文件夹modules

  • mkdir src/store/modules

在此文件夹中,创建productcart文件夹:

  • mkdir src/store/modules/product
  • mkdir src/store/modules/cart

这些将保存您的产品库存和您用户的购物车的所有状态文件。您将同时构建这两个文件,每个文件都在单独的终端中打开。这样,您将能够并排比较您的突变、getter 和操作。

最后,打开index.js文件product中的一个文件:

  • nano src/store/modules/product/index.js

添加以下代码以创建一个包含您的状态对象productItems

vuex-shopping-cart/src/store/modules/product/index.js
import axios from 'axios';
const state = {
  productItems: [] 
}

保存文件并保持打开状态。

同样,在新终端中,将一个index.js文件添加cart目录中,如下所示:

  • nano src/store/modules/cart/index.js

然后添加代码cartItems

vuex-shopping-cart/src/store/modules/cart/index.js
import axios from 'axios';
const state = {
  cartItems: []
}

保存此文件,但保持打开状态。

在这些代码片段中,您导入了 Axios 模块并设置了状态。state是一个存储对象保持一个需要的部件之间共享的应用级数据。

既然您已经设置了状态,请转到突变。

突变

突变是修改商店状态的方法。它们通常由一个字符串类型和一个接受状态和有效负载作为参数的处理程序组成。

您现在将为您的应用程序创建所有更改。

product/index.js在该state部分之后的文件中添加以下代码

vuex-shopping-cart/src/store/modules/product/index.js
...
const mutations = {
  UPDATE_PRODUCT_ITEMS (state, payload) {
    state.productItems = payload;
  }
}

这将创建一个mutations对象,对象包含一个UPDATE_PRODUCT_ITEMSproductItems数组设置为payload值的方法。

同样,在cart/index.js文件中 state 部分之后添加以下代码

vuex-shopping-cart/src/store/modules/cart/index.js
...
const mutations = {
  UPDATE_CART_ITEMS (state, payload) {
    state.cartItems = payload;
  }
}

这会UPDATE_CART_ITEMS为您用户的购物车创建一个类似的。请注意,这遵循Flux 架构风格,以大写字母引用突变。

行动

操作是处理突变的方法,因此突变与应用程序代码的其余部分隔离开来。

在 中product/index.jsactions为您的应用程序创建一个包含所有操作对象:

vuex-shopping-cart/src/store/modules/product/index.js
...
const actions = {
  getProductItems ({ commit }) {
    axios.get(`/api/products`).then((response) => {
      commit('UPDATE_PRODUCT_ITEMS', response.data)
    });
  }
}

这里该getProductItems方法GET使用您之前安装的 Axios 包向服务器发送异步请求。当请求成功时,将UPDATE_PRODUCT_ITEMS使用响应数据作为有效负载调用突变。

接下来,将以下actions对象添加cart/index.js

vuex-shopping-cart/src/store/modules/cart/index.js
...
const actions = {
  getCartItems ({ commit }) {
    axios.get('/api/cart').then((response) => {
      commit('UPDATE_CART_ITEMS', response.data)
    });
  },
  addCartItem ({ commit }, cartItem) {
    axios.post('/api/cart', cartItem).then((response) => {
      commit('UPDATE_CART_ITEMS', response.data)
    });
  },
  removeCartItem ({ commit }, cartItem) {
    axios.delete('/api/cart/delete', cartItem).then((response) => {
      commit('UPDATE_CART_ITEMS', response.data)
    });
  },
  removeAllCartItems ({ commit }) {
    axios.delete('/api/cart/delete/all').then((response) => {
      commit('UPDATE_CART_ITEMS', response.data)
    });
  }
}

在此文件中,您创建了getCartItemsGET服务器发送异步请求方法当请求成功时,将UPDATE_CART_ITEMS使用响应数据作为有效负载调用突变。removeAllCartItems方法也会发生同样的情况,尽管它DELETE向服务器发出请求。removeCartItemaddCartItem方法接收该cartItem对象作为用于使一个参数DELETEPOST要求。请求成功后,将UPDATE_CART_ITEMS使用响应数据作为有效负载调用更改。

您使用 ES6解构commit方法与 Vuexcontext对象分离。这类似于使用context.commit.

吸气剂

Getters 之于应用程序商店,就像计算属性之于组件。它们从涉及接收计算状态数据的存储状态方法返回计算信息。

接下来,创建一个getters对象来获取product模块的所有信息

vuex-shopping-cart/src/store/modules/product/index.js
...
const getters = {
  productItems: state => state.productItems,
  productItemById: (state) => (id) => {
    return state.productItems.find(productItem => productItem.id === id)
  }
}

在这里,您创建了一个productItems返回状态中产品项目列表的方法,然后是productItemById一个高阶函数,通过其返回单个产品id

接下来,在 中创建一个getters对象cart/index.js

vuex-shopping-cart/src/store/modules/cart/index.js
...
const getters = {
  cartItems: state => state.cartItems,
  cartTotal: state => {
    return state.cartItems.reduce((acc, cartItem) => {
      return (cartItem.quantity * cartItem.price) + acc;
    }, 0).toFixed(2);
  },
  cartQuantity: state => {
    return state.cartItems.reduce((acc, cartItem) => {
      return cartItem.quantity + acc;
    }, 0);
  }
}

在此代码段中,您创建了cartItems方法,该方法返回处于状态的购物车项目列表,然后是cartTotal,该方法返回可用于结帐的购物车项目总数的计算值。最后,您创建了cartQuantity重新调整购物车中商品数量方法。

导出模块

在最后一部分productcart模块将导出statemutationsactions,和getters对象,使应用程序的其他部分可以访问它们。

在 中product/index.js,在文件末尾添加以下代码:

vuex-shopping-cart/src/store/modules/product/index.js
...
const productModule = {
  state,
  mutations,
  actions,
  getters
}

export default productModule;

productModule会将所有状态对象收集到对象中,然后将其作为模块导出。

保存product/index.js并关闭文件。

接下来,将类似的代码添加到cart/index.js

vuex-shopping-cart/src/store/modules/product/index.js
...
    const cartModule = {
  state,
  mutations,
  actions,
  getters
}
export default cartModule;

这将模块导出为cartModule.

设置商店

状态、突变、动作和 getter 都设置好后,将 Vuex 集成到应用程序的最后一部分是创建商店。在这里,您将利用 Vuex 模块将您的应用程序商店拆分为两个可管理的片段。

要创建您的商店,请打开index.js文件store中的文件:

  • nano src/store/index.js

添加以下突出显示的行:

vuex-shopping-cart/src/store/index.js
import { createStore } from 'vuex'
import product from'./modules/product';
import cart from './modules/cart';

export default createStore({
  modules: {
    product,
    cart
  }
})

保存文件,然后退出文本编辑器。

您现在已经创建了状态管理所需的方法并为您的购物车创建了商店。接下来,您将创建用户界面 (UI) 组件来使用数据。

第 4 步 – 创建接口组件

现在您已经为购物车设置了商店,您可以继续为用户界面 (UI) 制作组件。这将包括对路由器进行一些更改,并为导航栏以及产品和购物车的列表和项目视图制作前端组件。

首先,您将更新您的vue-router设置。请记住,当您使用 Vue CLI 工具构建应用程序时,您选择了路由器选项,它允许 Vue 自动为您设置路由器。现在您可以重新配置路由器以提供Cart_List.vue和 的路径Product_List.vue,它们是您稍后将制作的 Vue 组件。

使用以下命令打开路由器文件:

  • nano vuex-shopping-cart/src/router/index.js

添加以下突出显示的行:

vuex-shopping-cart/src/router/index.js
import { createRouter, createWebHashHistory } from 'vue-router'
import CartList from '../components/cart/Cart_List.vue';
import ProductList from '../components/product/Product_List.vue';

const routes = [
  {
    path: '/inventory',
    component: ProductList
  },
  {
    path: '/cart',
    component: CartList
  },
  {
    path: '/',
    redirect: '/inventory'
  },
]
const router = createRouter({
  history: createWebHashHistory(),
  routes
})

export default router

这会/inventory为您的产品和/cart购物车中的商品创建路线它还会将您的根路径重定向/到产品视图。

添加此代码后,保存并关闭文件。

现在您可以设置您的 UI 组件目录。在终端上运行此命令以移动到组件的目录:

  • cd src/components

运行此命令以在组件目录下创建三个新子文件夹:

  • mkdir core cart product

core将包含应用程序的重要部分,例如导航栏。cart并且product将举行项目和列表视图购物车总库存。

core目录下,Navbar.vue通过运行以下命令创建文件:

  • touch core/Navbar.vue

cart目录下,创建文件Cart_List_Item.vueCart_List.vue

  • touch cart/Cart_List_Item.vue cart/Cart_List.vue

最后,在product目录下,创建这两个文件:

  • touch product/Product_List_Item.vue product/Product_List.vue

现在已经概述了文件结构,您可以继续创建前端应用程序的各个组件。

在导航栏中,购物车导航链接将显示购物车中的商品数量。您将使用 VuexmapGetters辅助方法直接将 store getter 与组件计算属性映射,从而允许您的应用程序将这些数据从 store 的 getter 获取到Navbar组件。

打开导航栏文件:

  • nano core/Navbar.vue

将代码替换为以下内容:

vuex-shopping-cart/src/components/core/Navbar.vue
<template>
    <nav class="navbar" role="navigation" aria-label="main navigation">
      <div class="navbar-brand">
        <a
          role="button"
          class="navbar-burger burger"
          aria-label="menu"
          aria-expanded="false"
          data-target="navbarBasicExample"
        >
          <span aria-hidden="true"></span>
          <span aria-hidden="true"></span>
          <span aria-hidden="true"></span>
        </a>
      </div>
      <div id="navbarBasicExample" class="navbar-menu">
        <div class="navbar-end">
          <div class="navbar-item">
            <div class="buttons">
              <router-link to="/inventory" class="button is-primary">
               <strong> Inventory</strong>
              </router-link>
              <router-link to="/cart"  class="button is-warning">   <p>
    Total cart items:
    <span> {{cartQuantity}}</span> </p>
              </router-link>
            </div>
          </div>
        </div>
      </div>
    </nav>
</template>
<script>
import {mapGetters} from "vuex"
export default {
    name: "Navbar",
    computed: {
    ...mapGetters([
      'cartQuantity'
    ])
  },
  created() {
    this.$store.dispatch("getCartItems");
  }
}
</script>

作为一个 Vue 组件,这个文件以一个template元素开始,它保存了组件的 HTML。此代码段包含多个navbar使用 Bulma CSS 框架中的预制样式的类。有关更多信息,请查看Bulma 文档

这也使用router-link元素将应用程序连接到您的产品和购物车,并cartQuantity用作计算属性来动态跟踪购物车中的商品数量。

JavaScript 保存在script元素中,元素还处理状态管理和导出组件。getCartItems创建导航栏组件时会调度操作,使用从服务器接收到的响应数据中的所有购物车项目更新商店状态。在此之后,商店 getter 重新计算它们的返回值并cartQuantity在模板中呈现。getCartItems在创建的生命周期钩子上不分派动作, 的值cartQuantity将是 0,直到商店状态被修改。

保存并关闭文件。

Product_List 成分

该组件是该组件的父级Product_List_Item它将负责将产品项目作为道具传递给Product_List_Item(子)组件。

首先,打开文件:

  • nano product/Product_List.vue

更新Product_List.vue如下:

vuex-shopping-cart/src/components/product/Product_List.vue
<template>
  <div class="container is-fluid">
    <div class="tile is-ancestor">
      <div class="tile is-parent" v-for="productItem in productItems" :key="productItem.id">
      <ProductListItem :productItem="productItem"/>
      </div>
    </div>
  </div>
</template>
<script>
import { mapGetters } from 'vuex';
import Product_List_Item from './Product_List_Item'
export default {
  name: "ProductList",
  components: {
    ProductListItem:Product_List_Item
  },
  computed: {
    ...mapGetters([
      'productItems'
    ])
  },
  created() {
    this.$store.dispatch('getProductItems');
  }
};
</script>

Navbar之前讨论组件逻辑类似,这里的 VuexmapGetters帮助器方法直接将存储 getter 与组件计算属性映射,以productItems从存储中获取数据。getProductItems操作在ProductList创建组件时被分派,使用从服务器接收到的响应数据中的所有产品项目更新商店状态。在此之后,商店 getter 重新计算它们的返回值并productItems在模板中呈现。如果没有getProductItems在创建的生命周期钩子上分派动作,模板中将不会显示任何产品项目,直到商店状态被修改。

Product_List_Item 成分

该组件将是该组件的直接子Product_List组件。它将productItem从其父级接收数据作为道具并在模板中呈现它们。

打开Product_List_Item.vue:

  • nano product/Product_List_Item.vue

然后添加以下代码:

vuex-shopping-cart/src/components/product/Product_List_Item.vue
<template>
    <div class="card">
      <div class="card-content">
        <div class="content">
          <h4>{{ productItem.title }}</h4>
          <a
            class="button is-rounded is-pulled-left"
            @click="addCartItem(productItem)"
          >
            <strong>Add to Cart</strong>
          </a>
          <br />
          <p class="mt-4">
            {{ productItem.description }}
          </p>
        </div>
        <div class="media">
          <div class="media-content">
            <p class="title is-6">{{ productItem.owner }}</p>
            <p class="subtitle is-7">{{ productItem.email }}</p>
          </div>
          <div class="media-right">
            <a class="button is-primary is-light">
              <strong>$ {{ productItem.price }}</strong>
            </a>
          </div>
        </div>
      </div>
    </div>
</template>
<script>
import {mapActions} from 'vuex'
export default {
  name: "ProductListItem",
  props: ["productItem"],
  methods: {
    ...mapActions(["addCartItem"]),
  },
};
</script>

除了mapGetters前面组件中用到helper 函数,Vuex 还为你提供了mapActionshelper 函数,可以直接将组件方法映射到store 的action。在这种情况下,您使用mapAction辅助函数将组件方法映射到addCartItem商店中操作。现在您可以将商品添加到购物车。

保存并关闭文件。

Cart_List 成分

该组件负责显示添加到购物车的所有产品项目以及从购物车中删除所有项目。

要创建这个组件,首先打开文件:

  • nano cart/Cart_List.vue

接下来,更新Cart_List.vue如下:

vuex-shopping-cart/src/components/cart/Cart_List.vue
<template>
  <div id="cart">
    <div class="cart--header has-text-centered">
      <i class="fa fa-2x fa-shopping-cart"></i>
    </div>
    <p v-if="!cartItems.length" class="cart-empty-text has-text-centered">
      Add some items to the cart!
    </p>
    <ul>
      <li class="cart-item" v-for="cartItem in cartItems" :key="cartItem.id">
          <CartListItem :cartItem="cartItem"/>
      </li>
      <div class="notification is-success">
        <button class="delete"></button>
        <p>
          Total Quantity:
          <span class="has-text-weight-bold">{{ cartQuantity }}</span>
        </p>
      </div>
      <br>
    </ul>
    <div class="buttons">
    <button :disabled="!cartItems.length" class="button is-info">
      Checkout (<span class="has-text-weight-bold">${{ cartTotal }}</span>)
    </button>

 <button class="button is-danger is-outlined" @click="removeAllCartItems">
    <span>Delete All items</span>
    <span class="icon is-small">
      <i class="fas fa-times"></i>
    </span>
  </button>
       </div>
  </div>
</template>
<script>
import { mapGetters, mapActions } from "vuex";
import CartListItem from "./Cart_List_Item";
export default {
  name: "CartList",
  components: {
    CartListItem
  },
  computed: {
    ...mapGetters(["cartItems", "cartTotal", "cartQuantity"]),
  },
  created() {
    this.$store.dispatch("getCartItems");
  },
  methods: {
    ...mapActions(["removeAllCartItems"]),
  }
};
</script>

此代码使用模板中v-if语句在购物车为空时有条件地呈现消息。否则,它会遍历购物车项目的存储并将它们呈现到页面。您还加载了cartItemscartTotalcartQuantitygetter 来计算数据属性,并引入了removeAllCartItems操作。

保存并关闭文件。

Cart_List_Item 成分

该组件是组件的直接子Cart_List组件。cartItem从其父级接收数据作为道具,并在模板中呈现它们。它还负责增加和减少购物车中商品的数量。

打开文件:

  • nano cart/Cart_List_Item.vue

更新Cart_List_Item.vue如下:

vuex-shopping-cart/src/components/cart/Cart_List_Item.vue
<template>
  <div class="box">
    <div class="cart-item__details">
      <p class="is-inline">{{cartItem.title}}</p>
      <div>
        <span class="cart-item--price has-text-info has-text-weight-bold">
          ${{cartItem.price}} X {{cartItem.quantity}}
        </span>

        <span>
          <i class="fa fa-arrow-circle-up cart-item__modify" @click="addCartItem(cartItem)"></i>
          <i class="fa fa-arrow-circle-down cart-item__modify" @click="removeCartItem(cartItem)"></i>
        </span>
      </div>

    </div>
  </div>
</template>
<script>
import { mapActions } from 'vuex';
export default {
  name: 'CartListItem',
  props: ['cartItem'],
  methods: {
    ...mapActions([
      'addCartItem',
      'removeCartItem'
    ])
  }
}
</script>

在这里,您使用mapAction辅助函数将组件方法映射到商店中addCartItemremoveCartItem动作。

保存并关闭文件。

最后,您将更新App.vue文件以将这些组件引入您的应用程序。首先,移回项目的根文件夹:

  • cd ../..

现在打开文件:

  • nano src/App.vue

将内容替换为以下代码:

vuex-shopping-cart/src/App.vue
<template>
  <div>
    <Navbar/>
    <div class="container mt-6">
      <div class="columns">
        <div class="column is-12 column--align-center">
          <router-view></router-view>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import Navbar from './components/core/Navbar'
export default {
  name: 'App',
  components: {
    Navbar
  }
}
</script>
<style>
html,
body {
  height: 100%;
  background: #f2f6fa;
}
</style>

App.vue是以 Vue 组件文件格式定义的应用程序的根。完成更改后,保存并关闭文件。

In this step, you set up the frontend of your shopping cart app by creating components for the navigation bar, the product inventory, and the shopping cart. You also used the store actions and getters that you created in a previous step. Next, you will get your application up and running.

Step 5 — Running the Application

Now that your app is ready, you can start the development server and try out the final product.

Run the following command in the root of your front-end project:

  • npm run serve

This will start a development server that allows you to view your app on http://localhost:8080. Also, make sure that your backend is running in a separate terminal; you can do this by running the following command in your cart-backend project:

  • node server

后端和前端运行后,http://localhost:8080在浏览器中导航到你会发现你的购物车应用程序:

用户在购物车应用程序中添加和删除产品的动画

结论

在本教程中,您使用 Vue.js 和 Vuex 构建了一个用于数据管理的在线购物车应用程序。这些技术可以重复使用以形成电子商务购物应用程序的基础。如果您想了解有关 Vue.js 的更多信息,请查看我们的Vue.js 主题页面

觉得文章有用?

点个广告表达一下你的爱意吧 !😁