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

作者选择了 开放源代码倡议以作为 写给捐款计划的一部分接受捐款。

介绍

Vue.js是一个功能强大的和 进展的 Javascript框架。 这是一个流行的框架在 GitHub和有一个活跃和有用的社区。

为了显示Vue网络框架的能力,这个教程将引导您通过建设电子商务应用程序的购物车. 此应用程序将存储产品信息并持有客户日后想购买的出厂产品. 为存储信息,您将探索为Vue.js:Vuex广泛使用的状态管理库. 这将允许购物车应用程序将数据持续到服务器. 您也将使用 Vuex 处理 [同步] (https://andsky.com/tech/tutorials/understanding-the-event-loop-callbacks-promises-and-async-await-in-javascript] 任务管理 .

一旦你完成了教程,你将有一个运行的购物车应用程序如下:

Animation of user adding and deleting products from the shopping cart application

前提条件

  • 联合国 您需要一个运行 [Node.js] (https://nodejs.org/en/about/) 的开发环境; 此教程在 Node.js 版本 10.22.0 和 npm 版本 6.14.6. 上进行了测试 。 要在 macOS 或 Ubuntu 18.04 上安装此功能,请遵循 [如何在 macOS (https://andsky.com/tech/tutorials/how-to-install-node-js-and-create-a-local-development-environment-on-macos 上安装节点并创建本地开发环境] 或 [如何在 Ubuntu 18.04 (https://andsky.com/tech/tutorials/how-to-install-node-js-on-ubuntu-18-04 上安装节点.js] 的 ** 部分使用 PPA
  • 联合国 您还需要JavaScript, HTML, CSS 的基本知识, 您可以在 [How To Build a website With HTML series] (https://www.digitalocean.com/community/tutorial_series/how-to-build-a-website-with-html), [How To Build a website With CSS series] (https://www.digitalocean.com/community/tutorial_series/how-to-build-a-website-with-css), 以及 [How To Code in JavaScript (https://www.digitalocean.com/community/tutorial_series/how-to-code-in-javascript) 中找到这些知识. .

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

从版本 4.5.0, Vue CLI现在提供了内置选项,在创建新项目时选择Vue 3预设。最新版本的Vue CLI允许您将Vue 3从盒子中使用,并将现有的Vue 2项目更新到Vue 3. 在此步骤中,您将使用Vue CLI创建您的项目,然后安装前端依赖。

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

1npm install -g @vue/cli

這將在您的系統上安裝 Vue CLI 全球。

注意:在某些系统中,在全球范围内安装 npm 套件可能会导致允许错误,这会中断安装。 由于使用 )。

检查你有正确的版本,使用这个命令:

1vue --version

你会得到这样的输出:

1[secondary_label Output]
2@vue/cli 4.5.10

注意: 如果您已经在全球范围内安装了 Vue CLI 的较旧版本,请从终端执行以下命令来升级:

1npm update -g @vue/cli

美元

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

1vue create vuex-shopping-cart

要了解更多关于 Vue CLI 的信息,请参阅 如何使用 Vue CLI 生成 Vue.js 单页应用程序

接下来,您将收到以下快递:

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

从此列表中选择手动选择功能选项。

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

 1[secondary_label Output]
 2...
 3 ◉ Choose Vue version
 4 ◯ Babel
 5 ◯ TypeScript
 6 ◯ Progressive Web App (PWA) Support
 7 ◉ Router
 8 ◉ Vuex
 9 ◯ CSS Pre-processors
10 ◯ Linter / Formatter
11❯◯ Unit Testing
12 ◯ E2E Testing

从此列表中,选择).

接下来,为您的Vue版本选择3.x(预览),对历史模式回答,并选择设置选项在专用配置文件中

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

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

1cd vuex-shopping-cart

首先,您将安装Bulma,一个基于Flexbox的免费开源CSS框架(https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox)。

1npm install bulma

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

1nano src/main.js

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

1[label vuex-shopping-cart/src/main.js]
2import { createApp } from 'vue'
3import App from './App.vue'
4import router from './router'
5import store from './store'
6import './../node_modules/bulma/css/bulma.css'
7
8createApp(App).use(store).use(router).mount('#app')

保存并关闭文件。

在此应用程序中,您将使用 Axios模块向您的服务器发送请求。

1npm install axios

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

1npm run serve

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

Default Vue page when running your app in development mode

一旦您确认Vue正在工作,请用CTRL+C关闭服务器。

在此步骤中,您在计算机上安装了 Vue CLI,创建了一个 Vue 项目,安装了所需的 npm 包 Axios 和 Bulma,并在 main.js 文件中导入了 Bulma。

步骤2 - 设置后端

在此步骤中,您将创建一个单独的后端,以便与Vue项目一起工作. 这将位于与前端Vue应用程序不同的项目文件夹中。

首先,走出您的Vue目录:

1cd ..

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

1mkdir cart-backend

一旦你有你的后端文件夹,让它成为你的工作目录:

1cd cart-backend

您将通过用所需的文件初始化项目开始创建您的应用程序的文件结构,使用以下命令:

1touch server.js
2touch server-cart-data.json
3touch server-product-data.json

你在这里使用)将保留商店的产品和用户的购物车的数据。

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

1npm init

有关 npm 和 Node 的更多信息,请查看我们的 如何在 Node.js 中编码 系列。

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

1npm install concurrently express body-parser

Express 是 Web 应用程序的 Node 框架,可为处理 API 请求提供有用的抽象信息。 Express 可用于同时运行 Express 后端服务器和 Vue.js 开发服务器。 最后, body-parser 是一种 Express 中间软件,可对您的 API 进行审查。

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

1nano server.js

然后添加以下代码:

 1[label cart-backend/server.js]
 2const express = require('express');
 3const bodyParser = require('body-parser');
 4const fs = require('fs');
 5const path = require('path');
 6
 7const app = express();
 8const PRODUCT_DATA_FILE = path.join(__dirname, 'server-product-data.json');
 9const CART_DATA_FILE = path.join(__dirname, 'server-cart-data.json');
10
11app.set('port', (process.env.PORT || 3000));
12app.use(bodyParser.json());
13app.use(bodyParser.urlencoded({ extended: true }));
14app.use((req, res, next) => {
15  res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
16  res.setHeader('Pragma', 'no-cache');
17  res.setHeader('Expires', '0');
18  next();
19});
20
21app.listen(app.get('port'), () => {
22  console.log(`Find the server at: http://localhost:${app.get('port')}/`);
23});

此片段首先会将节点模块添加到您的后端,包括写入您文件系统的"fs"模块和让定义文件路径更容易的"路径"模块. 然后,您将).

)。

接下来,您将创建一个 API 终端,您的前端将查询以将项目添加到购物车中. 为此,您将使用app.post来收听 HTTP POST 请求。

添加以下代码到)`中间软件:

 1[label cart-backend/server.js]
 2...
 3app.use((req, res, next) => {
 4  res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
 5  res.setHeader('Pragma', 'no-cache');
 6  res.setHeader('Expires', '0');
 7  next();
 8});
 9
10app.post('/cart', (req, res) => {
11    fs.readFile(CART_DATA_FILE, (err, data) => {
12      const cartProducts = JSON.parse(data);
13      const newCartProduct = { 
14        id: req.body.id,
15        title: req.body.title,
16        description: req.body.description,
17        price: req.body.price,
18        image_tag: req.body.image_tag, 
19        quantity: 1 
20      };
21      let cartProductExists = false;
22      cartProducts.map((cartProduct) => {
23        if (cartProduct.id === newCartProduct.id) {
24          cartProduct.quantity++;
25          cartProductExists = true;
26        }
27      });
28      if (!cartProductExists) cartProducts.push(newCartProduct);
29      fs.writeFile(CART_DATA_FILE, JSON.stringify(cartProducts, null, 4), () => {
30        res.setHeader('Cache-Control', 'no-cache');
31        res.json(cartProducts);
32      });
33    });
34  });
35
36app.listen(app.get('port'), () => {
37  console.log(`Find the server at: http://localhost:${app.get('port')}/`);
38});

此代码从前端接收了包含篮子项目的请求对象,并将其存储在您的项目根部的) 具有id,title,description,price,image_tagquantity属性。 代码还检查了篮子是否已经存在,以确保对重复产品的请求只增加了数量

现在,添加代码以创建 API 终端,从购物车中删除一个项目. 这一次,您将使用app.delete来听取 HTTP 请求DELETE

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

 1[label cart-backend/server.js]
 2...
 3      fs.writeFile(CART_DATA_FILE, JSON.stringify(cartProducts, null, 4), () => {
 4        res.setHeader('Cache-Control', 'no-cache');
 5        res.json(cartProducts);
 6      });
 7    });
 8  });
 9
10app.delete('/cart/delete', (req, res) => {
11  fs.readFile(CART_DATA_FILE, (err, data) => {
12    let cartProducts = JSON.parse(data);
13    cartProducts.map((cartProduct) => {
14      if (cartProduct.id === req.body.id && cartProduct.quantity > 1) {
15        cartProduct.quantity--;
16      } else if (cartProduct.id === req.body.id && cartProduct.quantity === 1) {
17        const cartIndexToRemove = cartProducts.findIndex(cartProduct => cartProduct.id === req.body.id);
18        cartProducts.splice(cartIndexToRemove, 1);
19      }
20    });
21    fs.writeFile(CART_DATA_FILE, JSON.stringify(cartProducts, null, 4), () => {
22      res.setHeader('Cache-Control', 'no-cache');
23      res.json(cartProducts);
24    });
25  });
26});
27
28app.listen(app.get('port'), () => {
29  console.log(`Find the server at: http://localhost:${app.get('port')}/`); // eslint-disable-line no-console
30});

此代码接收包含要从篮子中删除的项目的请求对象,并通过其id来检查该项目的server-cart-data.json文件。

为了为您的用户提供额外的功能,您现在可以创建一个 API 终端,从购物车中删除所有项目。

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

 1[label cart-backend/server.js]
 2...
 3    fs.writeFile(CART_DATA_FILE, JSON.stringify(cartProducts, null, 4), () => {
 4      res.setHeader('Cache-Control', 'no-cache');
 5      res.json(cartProducts);
 6    });
 7  });
 8});
 9
10app.delete('/cart/delete/all', (req, res) => {
11  fs.readFile(CART_DATA_FILE, () => {
12    let emptyCart = [];
13    fs.writeFile(CART_DATA_FILE, JSON.stringify(emptyCart, null, 4), () => {
14      res.json(emptyCart);
15    });
16  });
17});
18
19app.listen(app.get('port'), () => {
20  console.log(`Find the server at: http://localhost:${app.get('port')}/`); // eslint-disable-line no-console
21});

这个代码是负责通过返回一个空的(https://andsky.com/tech/tutorials/understanding-arrays-in-javascript)从篮子中删除所有项目。

接下来,您将创建一个 API 终端点,从产品存储中获取所有产品,此时您将使用 app.get 来收听一个GET请求。

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

 1[label cart-backend/server.js]
 2...
 3app.delete('/cart/delete/all', (req, res) => {
 4  fs.readFile(CART_DATA_FILE, () => {
 5    let emptyCart = [];
 6    fs.writeFile(CART_DATA_FILE, JSON.stringify(emptyCart, null, 4), () => {
 7      res.json(emptyCart);
 8    });
 9  });
10});
11
12app.get('/products', (req, res) => {
13  fs.readFile(PRODUCT_DATA_FILE, (err, data) => {
14    res.setHeader('Cache-Control', 'no-cache');
15    res.json(JSON.parse(data));
16  });
17});
18...

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

最后,您将创建一个 API 终端点,从篮子存储中获取所有项目:

 1[label cart-backend/server.js]
 2...
 3app.get('/products', (req, res) => {
 4  fs.readFile(PRODUCT_DATA_FILE, (err, data) => {
 5    res.setHeader('Cache-Control', 'no-cache');
 6    res.json(JSON.parse(data));
 7  });
 8});
 9
10app.get('/cart', (req, res) => {
11  fs.readFile(CART_DATA_FILE, (err, data) => {
12    res.setHeader('Cache-Control', 'no-cache');
13    res.json(JSON.parse(data));
14  });
15});
16...

类似地,这个代码使用文件系统的原始readFile方法来获取server-cart-data.json文件中的所有数据,并以JSON格式返回它们。

保存并关闭server.js文件。

接下来,您将为测试目的添加一些模仿数据到您的 JSON 文件。

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

1nano server-cart-data.json

添加以下产品对象:

 1[label cart-backend/server-cart-data.json]
 2[
 3    {
 4        "id": 2,
 5        "title": "MIKANO Engine",
 6        "description": "Lorem ipsum dolor sit amet, consectetur dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis! ",
 7        "price": 650.9,
 8        "image_tag": "diesel-engine.png",
 9        "quantity": 1
10    },
11    {
12        "id": 3,
13        "title": "SEFANG Engine",
14        "description": "Lorem ipsum dolor sit amet, consectetur dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis!",
15        "price": 619.9,
16        "image_tag": "sefang-engine.png",
17        "quantity": 1
18    }
19]

这显示了两个引擎,将从用户的购物车中开始。

保存并关闭文件。

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

1nano server-product-data.json

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

 1[label cart-backend/server-product-data.json]
 2[
 3    {
 4      "id": 1,
 5      "title": "CAT Engine",
 6      "description": "Lorem ipsum dolor sit amet, consectetur dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis!",
 7      "product_type": "power set/diesel engine",
 8      "image_tag": "CAT-engine.png",
 9      "created_at": 2020,
10      "owner": "Colton",
11      "owner_photo": "image-colton.jpg",
12      "email": "[email protected]",
13      "price": 719.9
14    },
15    {
16      "id": 2,
17      "title": "MIKANO Engine",
18      "description": "Lorem ipsum dolor sit amet, consectetur dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis! ",
19      "product_type": "power set/diesel engine",
20      "image_tag": "diesel-engine.png",
21      "created_at": 2020,
22      "owner": "Colton",
23      "owner_photo": "image-colton.jpg",
24      "email": "[email protected]",
25      "price": 650.9
26    },
27    {
28      "id": 3,
29      "title": "SEFANG Engine",
30      "description": "Lorem ipsum dolor sit amet, consectetur dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis!",
31      "product_type": "power set/diesel engine",
32      "image_tag": "sefang-engine.png",
33      "created_at": 2017,
34      "owner": "Anne",
35      "owner_photo": "image-anne.jpg",
36      "email": "[email protected]",
37      "price": 619.9
38    },
39    {
40      "id": 4,
41      "title": "CAT Engine",
42      "description": "Lorem ipsum dolor sit amet, consectetur dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis!",
43      "product_type": "power set/diesel engine",
44      "image_tag": "lawn-mower.png",
45      "created_at": 2017,
46      "owner": "Irene",
47      "owner_photo": "image-irene.jpg",
48      "email": "[email protected]",
49      "price": 319.9
50    }
51
52  ]

这将保留所有可能的产品,用户可以放入他们的购物车。

保存并关闭文件。

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

1node server

您将在您的终端上收到这样的东西:

1[secondary_label Output]
2Find the server at: http://localhost:3000/

让这个服务器在这个窗口中运行。

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

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

1cd ../vuex-shopping-cart

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

1nano vue.config.js

然后添加这个代码:

 1[label vuex-shopping-cart/vue.config.js]
 2module.exports = {
 3  devServer: {
 4    proxy: {
 5      '/api': {
 6        target: 'http://localhost:3000/',
 7        changeOrigin: true,
 8        pathRewrite: {
 9          '^/api': ''
10        }
11      }
12    }
13  }
14}

这将从您的前端发送请求到您的后端服务器在http://localhost:3000/. 有关代理配置的更多信息,请查看 [Vue devServer.proxy` 文档](https://cli.vuejs.org/config/#devserver-proxy)。

保存并关闭文件。

在此步骤中,您写了服务器侧代码,将处理您的购物车的API终端点. 您开始创建文件结构,并在server.js文件中添加必要的代码,并在您的JSON文件中添加数据。

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

在Vuex中,商店是存储应用程序的状态的地方。应用程序状态只能通过在组件中发送操作来更新,从而引发商店中的突变。

在此步骤中,您将构建这些零件,然后将所有东西合并成Vuex商店。

◎ 国家

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

您的项目根目录src中的仓库文件夹在项目设置时自动创建,然后在项目的src目录中找到仓库文件夹,然后创建一个名为模块的新文件夹:

1mkdir src/store/modules

在此文件夹中,创建产品文件夹:

1mkdir src/store/modules/product
2mkdir src/store/modules/cart

这些将保留您的产品库存和用户篮子的所有状态文件. 您将同时构建这两个文件,每个文件在一个单独的终端中打开。

最后,在产品文件夹中打开一个index.js文件:

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

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

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

保存文件并保持它打开。

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

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

然后为cartItems添加代码:

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

保存此檔案,但保持開放。

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

现在你已经设置了状态,转向突变。

突变

Mutations 是修改存储状态的方法,通常由一个字符串类型和一个处理器组成,将状态和负载作为参数接受。

您现在将为您的应用程序创建所有突变。

产品/index.js文件中,在状态部分之后,添加以下代码:

1[label vuex-shopping-cart/src/store/modules/product/index.js]
2...
3const mutations = {
4  UPDATE_PRODUCT_ITEMS (state, payload) {
5    state.productItems = payload;
6  }
7}

这会创建一个具有UPDATE_PRODUCT_ITEMS方法的突变对象,该对象将productItems数组设置为payload值。

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

1[label vuex-shopping-cart/src/store/modules/cart/index.js]
2...
3const mutations = {
4  UPDATE_CART_ITEMS (state, payload) {
5    state.cartItems = payload;
6  }
7}

这为用户的购物车创建了类似的)对大字母中的突变的引用风格。

行动

action 是处理突变的方法,使突变与您的应用程序代码的其余部分隔离。

产品/index.js中,创建一个操作对象,为您的应用程序创建所有操作:

1[label vuex-shopping-cart/src/store/modules/product/index.js]
2...
3const actions = {
4  getProductItems ({ commit }) {
5    axios.get(`/api/products`).then((response) => {
6      commit('UPDATE_PRODUCT_ITEMS', response.data)
7    });
8  }
9}

在这里,getProductItems 方法将使用您之前安装的 Axios 包向服务器发送非同步的GET请求。

接下来,将以下操作对象添加到cart/index.js:

 1[label vuex-shopping-cart/src/store/modules/cart/index.js]
 2...
 3const actions = {
 4  getCartItems ({ commit }) {
 5    axios.get('/api/cart').then((response) => {
 6      commit('UPDATE_CART_ITEMS', response.data)
 7    });
 8  },
 9  addCartItem ({ commit }, cartItem) {
10    axios.post('/api/cart', cartItem).then((response) => {
11      commit('UPDATE_CART_ITEMS', response.data)
12    });
13  },
14  removeCartItem ({ commit }, cartItem) {
15    axios.delete('/api/cart/delete', cartItem).then((response) => {
16      commit('UPDATE_CART_ITEMS', response.data)
17    });
18  },
19  removeAllCartItems ({ commit }) {
20    axios.delete('/api/cart/delete/all').then((response) => {
21      commit('UPDATE_CART_ITEMS', response.data)
22    });
23  }
24}

在此文件中, 您创建 GetCart 项目 方法, 向服务器发送同步的 GET 请求 。 当请求成功时,UPDATE_CART_ITEMS'突变称为以响应数据为有效载荷。 " removeAlleCart Projects " 方法也是如此,尽管它向服务器提出了 " DELETE " 请求。 " 移动Cart项目 " 和 " 添加Cart项目 " 方法得到 " 移动Cart项目 " 方法 项目反对作为提出DELETE'或POST'请求的参数。 在成功提出请求后,`UPDATE_CART_ITEMS'的突变被称为以响应数据为有效载荷.

您使用 ES6 destructuring来解开从 Vuex 对象中使用的 context.commit 方法。

《Getters》

Getters 是向应用商店返回一个组件的计算属性,它们返回来自存储状态方法的计算信息,涉及接收计算状态数据。

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

1[label vuex-shopping-cart/src/store/modules/product/index.js]
2...
3const getters = {
4  productItems: state => state.productItems,
5  productItemById: (state) => (id) => {
6    return state.productItems.find(productItem => productItem.id === id)
7  }
8}

在这里,您创建了一种方法productItems,该方法返回状态中的产品项目列表,其次是productItemById,这是一个更高的订单函数,该函数返回单个产品的id

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

 1[label vuex-shopping-cart/src/store/modules/cart/index.js]
 2...
 3const getters = {
 4  cartItems: state => state.cartItems,
 5  cartTotal: state => {
 6    return state.cartItems.reduce((acc, cartItem) => {
 7      return (cartItem.quantity * cartItem.price) + acc;
 8    }, 0).toFixed(2);
 9  },
10  cartQuantity: state => {
11    return state.cartItems.reduce((acc, cartItem) => {
12      return cartItem.quantity + acc;
13    }, 0);
14  }
15}

在本片中,您创建了cartItems方法,该方法返回了状态中的篮子项目列表,然后是cartTotal,该方法返回了可支出的篮子项目总数的计算值。

导出模块

产品模块的最后一部分将导出状态突变行动接口对象,以便应用程序的其他部分可以访问它们。

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

 1[label vuex-shopping-cart/src/store/modules/product/index.js]
 2...
 3const productModule = {
 4  state,
 5  mutations,
 6  actions,
 7  getters
 8}
 9
10export default productModule;

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

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

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

1[label vuex-shopping-cart/src/store/modules/product/index.js]
2...
3    const cartModule = {
4  state,
5  mutations,
6  actions,
7  getters
8}
9export default cartModule;

这将模块导出为cartModule

设置商店

随着状态,突变,动作和变量都设置,整合Vuex到您的应用程序的最终部分是创建商店. 在这里,您将利用Vuex模块将您的应用程序商店分为两个可管理的片段。

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

1nano src/store/index.js

添加以下突出的线条:

 1[label vuex-shopping-cart/src/store/index.js]
 2import { createStore } from 'vuex'
 3import product from'./modules/product';
 4import cart from './modules/cart';
 5
 6export default createStore({
 7  modules: {
 8    product,
 9    cart
10  }
11})

保存文件,然后离开文本编辑器。

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

步骤4:创建接口组件

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

首先,您将更新您的Vue 路由器设置. 请记住,当您使用 Vue CLI 工具来配置您的应用程序时,您选择了路由器选项,允许 Vue 自动为您设置路由器。

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

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

添加以下突出的线条:

 1[label vuex-shopping-cart/src/router/index.js]
 2import { createRouter, createWebHashHistory } from 'vue-router'
 3import CartList from '../components/cart/Cart_List.vue';
 4import ProductList from '../components/product/Product_List.vue';
 5
 6const routes = [
 7  {
 8    path: '/inventory',
 9    component: ProductList
10  },
11  {
12    path: '/cart',
13    component: CartList
14  },
15  {
16    path: '/',
17    redirect: '/inventory'
18  },
19]
20const router = createRouter({
21  history: createWebHashHistory(),
22  routes
23})
24
25export default router

这将为您的产品创建/库存路线和/cart路线,用于您的购物车中的项目,并将您的根路径/重定向到产品视图。

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

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

1cd src/components

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

1mkdir core cart product

核心将包含您的应用程序的基本部分,如导航栏。卡片产品将包含购物车的项目和列表视图以及总库存。

核心目录中,通过运行此命令创建Navbar.vue文件:

1touch core/Navbar.vue

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

1touch cart/Cart_List_Item.vue cart/Cart_List.vue

最后,在产品目录中,创建这两个文件:

1touch product/Product_List_Item.vue product/Product_List.vue

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

Navbar组件

您将使用VuexmapGetters辅助方法直接地图存储器与组件计算属性,允许您的应用程序从商店的输入器到Navbar组件获取这些数据。

打开 Navbar 文件:

1nano core/Navbar.vue

代替代码如下:

 1[label vuex-shopping-cart/src/components/core/Navbar.vue]
 2<template>
 3    <nav class="navbar" role="navigation" aria-label="main navigation">
 4      <div class="navbar-brand">
 5        <a
 6          role="button"
 7          class="navbar-burger burger"
 8          aria-label="menu"
 9          aria-expanded="false"
10          data-target="navbarBasicExample"
11        >
12          <span aria-hidden="true"></span>
13          <span aria-hidden="true"></span>
14          <span aria-hidden="true"></span>
15        </a>
16      </div>
17      <div id="navbarBasicExample" class="navbar-menu">
18        <div class="navbar-end">
19          <div class="navbar-item">
20            <div class="buttons">
21              <router-link to="/inventory" class="button is-primary">
22               <strong> Inventory</strong>
23              </router-link>
24              <router-link to="/cart"  class="button is-warning">   <p>
25    Total cart items:
26    <span> {{cartQuantity}}</span> </p>
27              </router-link>
28            </div>
29          </div>
30        </div>
31      </div>
32    </nav>
33</template>
34<script>
35import {mapGetters} from "vuex"
36export default {
37    name: "Navbar",
38    computed: {
39    ...mapGetters([
40      'cartQuantity'
41    ])
42  },
43  created() {
44    this.$store.dispatch("getCartItems");
45  }
46}
47</script>

作为Vue组件,此文件由一个)。

它还使用),以动态跟踪购物车中的项目数量。

JavaScript 保存在"script"元素中,它也处理国家管理并输出该组件. getCart Projects 动作在创建导航栏组件时被发送, 并用从服务器收到的响应数据中的所有卡车项目更新存储状态 。 在此之后,商店获得者重新计算其返回值,在模板中提供 " 数量 " 。 在不就创建的生命周期钩子发出GetCart Projects动作的情况下,在商店状态被修改之前,数量的值将是0.

保存并关闭文件。

产品_列表 组件

该组件是)组件。

首先,打开文件:

1nano product/Product_List.vue

更新「Product_List.vue」如下:

 1[label vuex-shopping-cart/src/components/product/Product_List.vue]
 2<template>
 3  <div class="container is-fluid">
 4    <div class="tile is-ancestor">
 5      <div class="tile is-parent" v-for="productItem in productItems" :key="productItem.id">
 6      <ProductListItem :productItem="productItem"/>
 7      </div>
 8    </div>
 9  </div>
10</template>
11<script>
12import { mapGetters } from 'vuex';
13import Product_List_Item from './Product_List_Item'
14export default {
15  name: "ProductList",
16  components: {
17    ProductListItem:Product_List_Item
18  },
19  computed: {
20    ...mapGetters([
21      'productItems'
22    ])
23  },
24  created() {
25    this.$store.dispatch('getProductItems');
26  }
27};
28</script>

与前面讨论的 " Navbar " 组件逻辑类似,这里的Vuex " mapGetters " 帮助方法直接映射出含有组件计算属性的存储获取器,以便从存储中获取 " 产品项目 " 数据。 " 生产项目 " 动作在创建 " 生产清单 " 组件时发出,用从服务器收到的响应数据的所有产品项目更新存储状态。 在此之后,商店获得者重新计算其返回值, " 产品项目 " 将在模板中提供。 如果不在创建的生命周期钩子上发出 GetProduction Projects 动作, 在模板中将不会显示产品项, 直到存储状态被修改 .

Product_List_Item 组件

该组件将是直接的产品_列表组件的子组件,它将收到产品项目数据作为其父母的补贴,并将其转换为模板。

打开Product_List_Item.vue:

1nano product/Product_List_Item.vue

然后添加以下代码:

 1[label vuex-shopping-cart/src/components/product/Product_List_Item.vue]
 2<template>
 3    <div class="card">
 4      <div class="card-content">
 5        <div class="content">
 6          <h4>{{ productItem.title }}</h4>
 7          <a
 8            class="button is-rounded is-pulled-left"
 9            @click="addCartItem(productItem)"
10          >
11            <strong>Add to Cart</strong>
12          </a>
13          <br />
14          <p class="mt-4">
15            {{ productItem.description }}
16          </p>
17        </div>
18        <div class="media">
19          <div class="media-content">
20            <p class="title is-6">{{ productItem.owner }}</p>
21            <p class="subtitle is-7">{{ productItem.email }}</p>
22          </div>
23          <div class="media-right">
24            <a class="button is-primary is-light">
25              <strong>$ {{ productItem.price }}</strong>
26            </a>
27          </div>
28        </div>
29      </div>
30    </div>
31</template>
32<script>
33import {mapActions} from 'vuex'
34export default {
35  name: "ProductListItem",
36  props: ["productItem"],
37  methods: {
38    ...mapActions(["addCartItem"]),
39  },
40};
41</script>

除了以前组件中使用的mapGetters辅助函数外,Vuex还为您提供mapActions辅助函数,可直接将组件方法绘制到商店的操作中。

保存并关闭文件。

Cart_List 组件

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

要创建此组件,先打开文件:

1nano cart/Cart_List.vue

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

 1[label vuex-shopping-cart/src/components/cart/Cart_List.vue]
 2<template>
 3  <div id="cart">
 4    <div class="cart--header has-text-centered">
 5      <i class="fa fa-2x fa-shopping-cart"></i>
 6    </div>
 7    <p v-if="!cartItems.length" class="cart-empty-text has-text-centered">
 8      Add some items to the cart!
 9    </p>
10    <ul>
11      <li class="cart-item" v-for="cartItem in cartItems" :key="cartItem.id">
12          <CartListItem :cartItem="cartItem"/>
13      </li>
14      <div class="notification is-success">
15        <button class="delete"></button>
16        <p>
17          Total Quantity:
18          <span class="has-text-weight-bold">{{ cartQuantity }}</span>
19        </p>
20      </div>
21      <br>
22    </ul>
23    <div class="buttons">
24    <button :disabled="!cartItems.length" class="button is-info">
25      Checkout (<span class="has-text-weight-bold">${{ cartTotal }}</span>)
26    </button>
27
28 <button class="button is-danger is-outlined" @click="removeAllCartItems">
29    <span>Delete All items</span>
30    <span class="icon is-small">
31      <i class="fas fa-times"></i>
32    </span>
33  </button>
34       </div>
35  </div>
36</template>
37<script>
38import { mapGetters, mapActions } from "vuex";
39import CartListItem from "./Cart_List_Item";
40export default {
41  name: "CartList",
42  components: {
43    CartListItem
44  },
45  computed: {
46    ...mapGetters(["cartItems", "cartTotal", "cartQuantity"]),
47  },
48  created() {
49    this.$store.dispatch("getCartItems");
50  },
51  methods: {
52    ...mapActions(["removeAllCartItems"]),
53  }
54};
55</script>

此代码使用模板中的 v-if 语句以条件渲染消息,如果篮子是空的。否则,它会迭代通过篮子存储的项目,并将其渲染到页面上。您还加载了 cartItemscartTotalcartQuantity' 接收器来计算数据属性,并引入了 removeAllCartItems` 操作。

保存并关闭文件。

Cart_List_Item 组件

该组件是Cart_List组件的直接子组件,它将cartItem数据从其母体中接收,并在模板中渲染。

打开文件:

1nano cart/Cart_List_Item.vue

更新「Cart_List_Item.vue」如下:

 1[label vuex-shopping-cart/src/components/cart/Cart_List_Item.vue]
 2<template>
 3  <div class="box">
 4    <div class="cart-item__details">
 5      <p class="is-inline">{{cartItem.title}}</p>
 6      <div>
 7        <span class="cart-item--price has-text-info has-text-weight-bold">
 8          ${{cartItem.price}} X {{cartItem.quantity}}
 9        </span>
10
11        <span>
12          <i class="fa fa-arrow-circle-up cart-item__modify" @click="addCartItem(cartItem)"></i>
13          <i class="fa fa-arrow-circle-down cart-item__modify" @click="removeCartItem(cartItem)"></i>
14        </span>
15      </div>
16
17    </div>
18  </div>
19</template>
20<script>
21import { mapActions } from 'vuex';
22export default {
23  name: 'CartListItem',
24  props: ['cartItem'],
25  methods: {
26    ...mapActions([
27      'addCartItem',
28      'removeCartItem'
29    ])
30  }
31}
32</script>

在这里,您正在使用mapAction辅助函数将组件方法绘制到商店中的addCartItemremoveCartItem操作。

保存并关闭文件。

最后,您将更新App.vue文件,将这些组件带入您的应用程序。

1cd ../..

现在打开文件:

1nano src/App.vue

用以下代码取代内容:

 1[label vuex-shopping-cart/src/App.vue]
 2<template>
 3  <div>
 4    <Navbar/>
 5    <div class="container mt-6">
 6      <div class="columns">
 7        <div class="column is-12 column--align-center">
 8          <router-view></router-view>
 9        </div>
10      </div>
11    </div>
12  </div>
13</template>
14<script>
15import Navbar from './components/core/Navbar'
16export default {
17  name: 'App',
18  components: {
19    Navbar
20  }
21}
22</script>
23<style>
24html,
25body {
26  height: 100%;
27  background: #f2f6fa;
28}
29</style>

App.vue是您在 Vue 组件文件格式中定义的应用程序的根。

在此步骤中,您通过创建导航栏、产品库存和购物车的组件来设置购物车应用程序的前端。

步骤5 - 运行应用程序

现在您的应用已经准备好了,您可以启动开发服务器并尝试最终的产品。

在您的前端项目的根中运行以下命令:

1npm run serve

这将启动一个开发服务器,允许您在http://localhost:8080上查看您的应用程序。另外,请确保您的后端在一个单独的终端中运行;您可以通过在您的cart-backend`项目中运行以下命令:

1node server

一旦你的后端和前端运行,在你的浏览器中导航到http://localhost:8080

Animation of user adding and deleting products from the shopping cart application

结论

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

Published At
Categories with 技术
comments powered by Disqus