作为Write for DOnations计划的一部分,作者选择了开源计划来接受捐赠。
介绍
Vue.js是一个高性能的渐进式 Javascript框架。它是GitHub 上的一个流行框架,拥有一个活跃且乐于助人的社区。
为了展示 Vue Web 框架的功能,本教程将引导您构建电子商务应用程序的购物车。此应用程序将存储产品信息并保存客户想要购买的产品以供稍后结账。为了存储信息,您将探索一个广泛使用的 Vue.js 状态管理库:Vuex。这将允许购物车应用程序将数据保存到服务器。您还将使用 Vuex处理异步任务管理。
完成本教程后,您将拥有一个功能正常的购物车应用程序,如下所示:
先决条件
-
你需要一个运行Node.js的开发环境;本教程在 Node.js 版本 10.22.0 和 npm 版本 6.14.6 上进行了测试。要在 macOS 或 Ubuntu 18.04 上安装它,请按照如何在 macOS 上安装 Node.js 和创建本地开发环境或如何在 Ubuntu 18.04 上安装 Node.js 的使用 PPA 安装部分中的步骤进行操作。
-
您还需要具备 JavaScript、HTML 和 CSS 的基本知识,您可以在我们的如何使用 HTML 系列构建网站、如何使用 CSS 系列构建网站和如何使用 JavaScript 编码中找到这些知识。
步骤 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 包会导致权限错误,这会中断安装。由于避免使用sudo
with是一种安全最佳实践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 单页应用程序。
接下来,您将收到以下提示:
OutputVue 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 version
,Router
和Vuex
。这将允许您选择您的 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
然后添加以下突出显示的导入行:
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 正在运行,请使用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
然后添加以下代码:
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_FILE
和CART_DATA_FILE
。这些将用作数据存储库。最后,您创建了一个 Express 服务器,设置了端口,创建了一个中间件来设置响应头,并将服务器设置为侦听您的端口。有关 Express 的更多信息,请参阅Express 官方文档。
该setHeader
方法设置 HTTP 响应的标头。在这种情况下,您使用Cache-Control
来指导应用程序的缓存。有关这方面的更多信息,请查看Mozilla 开发人员网络关于 Cache-Control 的文章。
接下来,您将创建一个 API 端点,您的前端将查询该端点以将商品添加到购物车。为此,您将使用app.post
侦听 HTTPPOST
请求。
server.js
在最后一个app.use()
中间件之后添加以下代码:
...
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的对象有id
,title
,description
,price
,image_tag
,和quantity
属性。该代码还会检查购物车是否已经存在,以确保对重复产品的请求只会增加quantity
.
现在,添加代码以创建 API 端点以从购物车中删除项目。这一次,您将使用app.delete
来侦听 HTTPDELETE
请求。
将以下代码添加到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
上一个端点之后:
...
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
上一个端点之后:
...
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 端点以从购物车存储中检索所有项目:
...
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
添加以下产品对象数组:
[
{
"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
文件中添加以下数据:
[
{
"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
您将在终端上收到类似的信息:
OutputFind the server at: http://localhost:3000/
让此服务器在此窗口中运行。
最后,您将在 Vue 应用程序中设置代理服务器。这将启用前端和后端之间的连接。
转到您的 Vue 应用程序的根目录:
- cd ../vuex-shopping-cart
在终端中,运行此命令以创建 Vue 配置文件:
- nano 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
中的文件夹是在项目设置时自动创建的。store
在src
项目目录中找到该文件夹,然后创建一个名为 的新文件夹modules
:
- mkdir src/store/modules
在此文件夹中,创建product
和cart
文件夹:
- mkdir src/store/modules/product
- mkdir src/store/modules/cart
这些将保存您的产品库存和您用户的购物车的所有状态文件。您将同时构建这两个文件,每个文件都在单独的终端中打开。这样,您将能够并排比较您的突变、getter 和操作。
最后,打开index.js
文件product
夹中的一个文件:
- nano src/store/modules/product/index.js
添加以下代码以创建一个包含您的状态对象productItems
:
import axios from 'axios';
const state = {
productItems: []
}
保存文件并保持打开状态。
同样,在新终端中,将一个index.js
文件添加到cart
目录中,如下所示:
- nano src/store/modules/cart/index.js
然后添加代码cartItems
:
import axios from 'axios';
const state = {
cartItems: []
}
保存此文件,但保持打开状态。
在这些代码片段中,您导入了 Axios 模块并设置了状态。的state
是一个存储对象保持一个需要的部件之间共享的应用级数据。
既然您已经设置了状态,请转到突变。
突变
突变是修改商店状态的方法。它们通常由一个字符串类型和一个接受状态和有效负载作为参数的处理程序组成。
您现在将为您的应用程序创建所有更改。
product/index.js
在该state
部分之后的文件中添加以下代码:
...
const mutations = {
UPDATE_PRODUCT_ITEMS (state, payload) {
state.productItems = payload;
}
}
这将创建一个mutations
对象,该对象包含一个UPDATE_PRODUCT_ITEMS
将productItems
数组设置为payload
值的方法。
同样,在cart/index.js
文件中 state 部分之后添加以下代码:
...
const mutations = {
UPDATE_CART_ITEMS (state, payload) {
state.cartItems = payload;
}
}
这会UPDATE_CART_ITEMS
为您用户的购物车创建一个类似的。请注意,这遵循Flux 架构风格,以大写字母引用突变。
行动
操作是处理突变的方法,因此突变与应用程序代码的其余部分隔离开来。
在 中product/index.js
,actions
为您的应用程序创建一个包含所有操作的对象:
...
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
:
...
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)
});
}
}
在此文件中,您创建了getCartItems
向GET
服务器发送异步请求的方法。当请求成功时,将UPDATE_CART_ITEMS
使用响应数据作为有效负载调用突变。该removeAllCartItems
方法也会发生同样的情况,尽管它DELETE
向服务器发出请求。的removeCartItem
和addCartItem
方法接收该cartItem
对象作为用于使一个参数DELETE
或POST
要求。请求成功后,将UPDATE_CART_ITEMS
使用响应数据作为有效负载调用更改。
您使用 ES6解构将commit
方法与 Vuexcontext
对象分离。这类似于使用context.commit
.
吸气剂
Getters 之于应用程序商店,就像计算属性之于组件。它们从涉及接收计算状态数据的存储状态方法返回计算信息。
接下来,创建一个getters
对象来获取product
模块的所有信息:
...
const getters = {
productItems: state => state.productItems,
productItemById: (state) => (id) => {
return state.productItems.find(productItem => productItem.id === id)
}
}
在这里,您创建了一个productItems
返回状态中产品项目列表的方法,然后是productItemById
一个高阶函数,通过其返回单个产品id
。
接下来,在 中创建一个getters
对象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
重新调整购物车中商品数量的方法。
导出模块
在最后一部分product
和cart
模块将导出state
,mutations
,actions
,和getters
对象,使应用程序的其他部分可以访问它们。
在 中product/index.js
,在文件末尾添加以下代码:
...
const productModule = {
state,
mutations,
actions,
getters
}
export default productModule;
这productModule
会将所有状态对象收集到对象中,然后将其作为模块导出。
保存product/index.js
并关闭文件。
接下来,将类似的代码添加到cart/index.js
:
...
const cartModule = {
state,
mutations,
actions,
getters
}
export default cartModule;
这将模块导出为cartModule
.
设置商店
状态、突变、动作和 getter 都设置好后,将 Vuex 集成到应用程序的最后一部分是创建商店。在这里,您将利用 Vuex 模块将您的应用程序商店拆分为两个可管理的片段。
要创建您的商店,请打开index.js
文件store
夹中的文件:
- nano 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
添加以下突出显示的行:
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.vue
和Cart_List.vue
:
- touch cart/Cart_List_Item.vue cart/Cart_List.vue
最后,在product
目录下,创建这两个文件:
- touch product/Product_List_Item.vue product/Product_List.vue
现在已经概述了文件结构,您可以继续创建前端应用程序的各个组件。
Navbar
成分
在导航栏中,购物车导航链接将显示购物车中的商品数量。您将使用 VuexmapGetters
辅助方法直接将 store getter 与组件计算属性映射,从而允许您的应用程序将这些数据从 store 的 getter 获取到Navbar
组件。
打开导航栏文件:
- nano 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
如下:
<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
然后添加以下代码:
<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 还为你提供了mapActions
helper 函数,可以直接将组件方法映射到store 的action。在这种情况下,您使用mapAction
辅助函数将组件方法映射到addCartItem
商店中的操作。现在您可以将商品添加到购物车。
保存并关闭文件。
Cart_List
成分
该组件负责显示添加到购物车的所有产品项目以及从购物车中删除所有项目。
要创建这个组件,首先打开文件:
- nano cart/Cart_List.vue
接下来,更新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
语句在购物车为空时有条件地呈现消息。否则,它会遍历购物车项目的存储并将它们呈现到页面。您还加载了cartItems
、cartTotal
和cartQuantity
getter 来计算数据属性,并引入了removeAllCartItems
操作。
保存并关闭文件。
Cart_List_Item
成分
该组件是组件的直接子Cart_List
组件。它cartItem
从其父级接收数据作为道具,并在模板中呈现它们。它还负责增加和减少购物车中商品的数量。
打开文件:
- nano cart/Cart_List_Item.vue
更新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
辅助函数将组件方法映射到商店中的addCartItem
和removeCartItem
动作。
保存并关闭文件。
最后,您将更新App.vue
文件以将这些组件引入您的应用程序。首先,移回项目的根文件夹:
- cd ../..
现在打开文件:
- nano 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 主题页面。