如何用Vue和Dinero.js建立购物车
入门
对于这个项目,我们将使用 vue-cli 和简单的 webapp-Vue.js 模板。如果您的计算机上没有全局安装 vue-cli,请启动您的终端并输入以下内容:
npm install -g vue-cli
然后:
vue init webpack-simple path/to/my-project
您可以保留所有问题的默认选项。完成后,导航到新目录,安装依赖项,然后运行项目:
cd path/to/my-project npm install npm run dev
Webpack 将开始在端口上为您的项目提供服务 8080(如果可用)并在您的浏览器中打开它。
设置 HTML/CSS
在本教程中,我不会涉及页面结构和样式,所以我邀请您复制/粘贴代码。打开 App.vue 文件并粘贴以下代码片段。
这在<template>标签之间:
<div id =“app”> <div class =“cart”> <h1 class =“title”>订单</ h1> <ul class =“items”> <li class =“item”> <div class =“ item-preview“> <img src =”“alt =”“class =”item-thumbnail“> <div> <h2 class =”item-title“> </ h2> <p class =”item-description“> </ p> </ div> </ div> <div> <input type =“text”class =“item-quantity”> <span class =“item-price”> </ span> </ DIV> </ LI> </ UL> <H3 类= “购物车线”> 小计<跨度类= “购物价格”> </跨度> </ H3> <H3 类= “购物车线” > 运费<span class =“cart-price”> </ span> </ h3> <h3 class =“cart-line”> 总计<span class =“cart-price cart-total”> </ span> </ h3 > </ div> </ div>
CSS 样式:
body { margin: 0; background: #fdca40; padding: 30px; } .title { display: flex; justify-content: space-between; align-items: center; margin: 0; text-transform: uppercase; font-size: 110%; font-weight: normal; } .items { margin: 0; padding: 0; list-style: none; } .cart { background: #fff; font-family: 'Helvetica Neue', Arial, sans-serif; font-size: 16px; color: #333a45; border-radius: 3px; padding: 30px; } .cart-line { display: flex; justify-content: space-between; align-items: center; margin: 20px 0 0 0; font-size: inherit; font-weight: normal; color: rgba(51, 58, 69, 0.8); } .cart-price { color: #333a45; } .cart-total { font-size: 130%; } .item { display: flex; justify-content: space-between; align-items: center; padding: 15px 0; border-bottom: 2px solid rgba(51, 58, 69, 0.1); } .item-preview { display: flex; align-items: center; } .item-thumbnail { margin-right: 20px; border-radius: 3px; } .item-title { margin: 0 0 10px 0; font-size: inherit; } .item-description { margin: 0; color: rgba(51, 58, 69, 0.6); } .item-quantity { max-width: 30px; padding: 8px 12px; font-size: inherit; color: rgba(51, 58, 69, 0.8); border: 2px solid rgba(51, 58, 69, 0.1); border-radius: 3px; text-align: center; } .item-price { margin-left: 20px; }
添加数据
在处理产品时,您通常会从数据库或 API 中检索原始数据。我们可以通过在单独的 JSON 文件中表示它,然后异步地导入它,就像查询 API 一样。
我们创建一个 products.json 文件 assets/并添加以下内容:
{ "items": [ { "title": "Item 1", "description": "A wonderful product", "thumbnail": "https://fakeimg.pl/80x80", "quantity": 1, "price": 20 }, { "title": "Item 2", "description": "A wonderful product", "thumbnail": "https://fakeimg.pl/80x80", "quantity": 1, "price": 15 }, { "title": "Item 3", "description": "A wonderful product", "thumbnail": "https://fakeimg.pl/80x80", "quantity": 2, "price": 10 } ], "shippingPrice": 20 }
这与我们从真实 API 获得的数据非常相似:数据作为集合,标题和文本作为字符串,数量和价格作为数字。
我们可以返回 App.vue 并设置空值 data。这将允许模板在获取实际数据时进行初始化。
data(){ return { data:{ items:[], shippingPrice:0 } } }
最后,我们可以从 products.json 异步请求中获取数据,并 data 在其准备就绪时更新属性:
export default { ... created() { fetch('./src/assets/products.json') .then(response => response.json()) .then(json => (this.data = json)) } }
现在让我们用这些数据填充我们的模板:
<ul class="items"> <li :key="item.id" v-for="item in data.items" class="item"> <div class="item-preview"> <img :src="item.thumbnail" :alt="item.title" class="item-thumbnail"> <div> <h2 class="item-title">{{ item.title }}</h2> <p class="item-description">{{ item.description }}</p> </div> </div> <div> <input type="text" class="item-quantity" v-model="item.quantity"> <span class="item-price">{{ item.price }}</span> </div> </li> </ul> ... <h3 class="cart-line"> Shipping <span class="cart-price">{{ data.shippingPrice }}</span> </h3> ...
你应该看到你的购物车中的所有物品。现在让我们添加一些计算属性来计算小计和总计:
export default { ... computed: { getSubtotal() { return this.data.items.reduce( (a, b) => a + b.price * b.quantity, 0 ) }, getTotal() { return ( this.getSubtotal + this.data.shippingPrice ) } } }
并将它们添加到我们的模板中:
<h3 class="cart-line"> 小计 <span class="cart-price">{{ getSubtotal }}</span> </h3> ... <h3 class="cart-line"> 总计 <span class="cart-price cart-total">{{ getTotal }}</span> </h3>
尝试改变数量-你应该看到小计和总金额相应变化。
现在我们在这里有几个问题。首先,我们只显示金额,而不是货币。当然,我们可以在反应量旁边的模板中对它们进行硬编码。但是如果我们想制作一个多语言网站呢?并非所有的语言都以相同的方式进行金钱化
如果我们想要显示所有金额的小数点后两位,以便更好地对齐?您可以尝试使用该 toFixed 方法将所有初始金额保留为浮点数,但那时您将使用的 String 类型在进行数学运算时难度更大,性能更低。而且,这意味着为了纯粹的表达目的而改变数据,这从来不是一个好主意。如果您需要将相同的数据用于其他目的并且需要不同的格式?
最后,当前的解决方案依赖于浮点数学,这对于处理金钱来说是个坏主意。尝试并更改一些金额:
{ "items": [ { ... "price": 20.01 }, { ... "price": 15.03 }, ... ] }
现在,看看您的购物车是如何损坏的?这不是一些错误的 JavaScript 行为,而是我们如何用二进制机器表示我们的小数编号系统的限制。如果你用花车做数学,你迟早会遇到那些不准确的。
好消息是,我们不必使用花车来存储钱。这正是 Dinero.js 发挥作用的地方。
Dinero.js,金钱包装
Dinero.js 之于金钱,正如 Moment.js 之于日期。它是一个库,允许您创建货币价值对象、操作它们、向它们提问并格式化它们。它依赖于 Martin Fowler 的货币模式,并帮助您解决由浮点数引起的所有常见问题,主要方法是将金额以整数的形式存储在较小的货币单位中。
打开终端,安装 Dinero.js:
npm install dinero.js --save
然后将其导入到 App.vue:
import Dinero from 'dinero.js' export default { ... }
现在可以创建 Dinero 对象:
// 返回 Dinero 对象,金额为$ 50 Dinero({ amount: 500, currency: 'USD' }) // 返回$ 4,000.00 Dinero({ amount: 500 }) .add(Dinero({ amount: 500 })) .multiply(4) .toFormat()
让我们创建一个工厂方法,将我们的 price 属性按需转换为 Dinero 对象。我们有最多两位小数的浮点数。这意味着如果我们想要以次要货币单位(在我们的情况下为美元)将它们转换为它们的等价物,我们需要将它们乘以 10 来乘以 2 的幂。
我们将该 factor 参数作为参数传递给默认值,以便我们可以使用具有不同指数的货币的方法。
export default { ... methods: { toPrice(amount, factor = Math.pow(10, 2)) { return Dinero({ amount: amount * factor }) } } }
美元是默认货币,所以我们不需要指定它。
因为我们在转换过程中正在进行浮点数学运算,所以有些计算结果可能会稍微不准确。通过将结果四舍五入到最接近的整数很容易解决。
toPrice(amount, factor = Math.pow(10, 2)) { return Dinero({ amount: Math.round(amount * factor) }) }
现在我们可以 toPrice 在我们的计算属性中使用:
export default { ... computed: { getShippingPrice() { return this.toPrice(this.data.shippingPrice) }, getSubtotal() { return this.data.items.reduce( (a, b) => a.add( this.toPrice(b.price).multiply(b.quantity) ), Dinero() ) }, getTotal() { return this.getSubtotal.add(this.getShippingPrice) } } }
在我们的模板中:
<ul class="items"> <li :key="item.id" v-for="item in data.items" class="item"> <div class="item-preview"> <img :src="item.thumbnail" :alt="item.title" class="item-thumbnail"> <div> <h2 class="item-title">{{ item.title }}</h2> <p class="item-description">{{ item.description }}</p> </div> </div> <div> <input type="text" class="item-quantity" v-model="item.quantity"> <span class="item-price">{{ toPrice(item.price) }}</span> </div> </li> </ul> <h3 class="cart-line"> Subtotal <span class="cart-price">{{ getSubtotal }}</span> </h3> <h3 class="cart-line"> Shipping <span class="cart-price">{{ getShippingPrice }}</span> </h3> <h3 class="cart-line"> Total <span class="cart-price cart-total">{{ getTotal }}</span> </h3>
如果你看看你的购物车,你会看到{}价格的地方。这是因为我们试图显示一个对象。相反,我们需要对它们进行格式化,以便它们可以使用正确的语法显示价格以及货币符号。
我们可以用 Dinero 的 toFormat 方法来实现这一点。
<ul class="items"> <li :key="item.id" v-for="item in data.items" class="item"> ... <div> ... <span class="item-price"> {{ toPrice(item.price).toFormat() }} </span> </div> </li> </ul> <h3 class="cart-line"> Subtotal <span class="cart-price"> {{ getSubtotal.toFormat() }} </span> </h3> <h3 class="cart-line"> Shipping <span class="cart-price"> {{ getShippingPrice.toFormat() }} </span> </h3> <h3 class="cart-line"> Total <span class="cart-price cart-total"> {{ getTotal.toFormat() }} </span> </h3>
现在你已经掌握了 Dinero.js 的基本知识,现在有时间来提升吧。
介绍
让我们 shippingPrice 转到 0JSON 文件。您的购物车现在应该显示“运费:$0.00”,这是准确的,但不便于用户使用。它会说“免费”更好吗?
幸运的是,Dinero.js 有很多方便的方法来向您的实例提问。在我们的例子中,这个 isZero 方法正是我们需要的。
在模板中,只要表示零,就可以显示文本而不是格式化的 Dinero 对象:
<h3 class="cart-line"> Shipping <span class="cart-price"> {{ getShippingPrice.isZero() ? 'Free' : getShippingPrice.setLocale(getLocale).toFormat() }} </span> </h3>
当然,你可以通过将它包装在一个方法中来概括这种行为。这将需要一个 Dinero 对象作为参数并返回一个 String。这样,只要您尝试显示零金额,就可以显示“免费”。
区域设置切换
想象一下你正在制作一个电子商务网站。你想适应你的国际观众,所以你翻译内容并添加一个语言切换器。但是,有一个细节可能会引起您的注意:货币格式也会根据语言而变化。例如,在美国英语中 10.00 欧元翻译为法语中的 10,00 欧元。
Dinero.js 通过 I18n API 支持国际格式。这可让您以本地化格式显示金额。
Dinero.js 是不可变的,所以我们不能依靠改变 Dinero.globalLocale 来重新格式化所有现有的实例。相反,我们需要使用该 setLocale 方法。
首先,我们添加了一个新的属性 language 中 data,并将其设置为默认值。对于语言环境,您需要使用 BCP 47 语言标签,如 en-US。
data() { return { data: { ... }, language: 'en-US' } }
现在我们可以直接在 Dinero 对象上使用 setLocale。当语言发生变化时,格式也会发生变化。
export default { ... methods: { toPrice(amount, factor = Math.pow(10, 2)) { return Dinero({ amount: Math.round(amount * factor) }) .setLocale(this.language) } }, computed: { ... getSubtotal() { return this.data.items.reduce( (a, b) => a.add( this.toPrice(b.price).multiply(b.quantity) ), Dinero().setLocale(this.language) ) }, ... } }
我们只需要在 toPrice 和 getSubtotal 中添加 setLocale,这是我们创建 Dinero 对象的唯一位置。
现在我们可以添加语言切换器:
// HTML <h1 class="title"> Order <span> <span class="language" @click="language = 'en-US'">English</span> <span class="language" @click="language = 'fr-FR'">French</span> </span> </h1> // CSS .language { margin: 0 2px; font-size: 60%; color: rgba(#333a45, 0.6); text-decoration: underline; cursor: pointer; }
当您单击切换器时,它将重新分配语言,这将更改对象的格式化方式。因为库是不可变的,这将返回新的对象,而不是更改现有的对象。这意味着,如果您创建一个 Dinero 对象并决定在某个地方显示它,然后在其他地方引用它并在其上应用 setLocale,那么您的初始实例将不会受到影响。没有讨厌的副作用!
所有含税
在购物车上看到一条税收线是很常见的。你可以用百分比法添加 Dinero.js
首先,让我们在 JSON 文件中添加一个 vatRate 属性:
{ ... "vatRate": 20 }
数据的初值:
data() { return { data: { ... vatRate: 0 } } }
现在我们可以用这个值来计算我们的带税购物车的总数。首先,我们需要创建一个 getTaxAmount computed 属性。然后我们也可以将它添加到 getTotal 中。
export default { ... computed: { getTaxAmount() { return this.getSubtotal.percentage(this.data.vatRate) }, getTotal() { return this.getSubtotal .add(this.getTaxAmount) .add(this.getShippingPrice) } } }
购物车现在显示了含税总额。我们也可以加一行来显示税额是多少:
<h3 class="cart-line"> VAT ({{ data.vatRate }}%) <span class="cart-price">{{ getTaxAmount.toFormat() }}</span> </h3>
结束语
到这儿文章就将如何用 Vue 和 Dinero.js 建立购物车就完成了!小编带大家已经探讨了 Dinero 的几个概念。但这只是它所能提供的服务的皮毛。如果你感兴趣的话可以阅读文档并在 GitHub 上查看项目。
码云笔记 » 如何用Vue和Dinero.js建立购物车