vue.js购物车计算_如何使用Vue和Dinero.js构建购物车

vue.js购物车计算

by Sarah Dayan

通过莎拉·达扬

如何使用Vue和Dinero.js构建购物车 (How to build a shopping cart with Vue and Dinero.js)

My friend Cory and I chat almost every day, so you can bet he knows about everything going on in my life. But as we were talking the other day, I realized he had no idea how Dinero.js, my latest project, actually works. Like, what you can do with it.

我和我的朋友Cory几乎每天聊天,所以您可以打赌他知道我一生中发生的一切。 但是,就在前几天我们谈论时,我意识到他不知道我最新的项目Dinero.js是如何工作的 。 就像,您可以用它做什么。

I paused and realized it may actually not be that obvious. It’s easier, whatever your skill level is, to understand what a smooth scrolling plugin does than what a money library has to offer.

我停下来,意识到实际上可能并不那么明显。 不管您的技能水平如何,要理解平滑滚动插件的功能要比钱库提供的功能容易得多。

“Do you see in JavaScript how you can use a Date constructor to store a date and format it later? Or you use Moment.js to create moment objects and how it’s better than storing dates as strings or any other type?
“您在JavaScript中看到如何使用Date构造函数存储日期并在以后格式化它吗? 还是您使用Moment.js创建了矩对象,它比将日期存储为字符串或任何其他类型的方法更好吗?

Well, Dinero.js is like Moment, but for money. There’s no native way to handle money, and if you try to do it with Number types, you’re going to run into issues. That’s what Dinero.js helps you avoid. It secures your monetary values in objects and allows you to do whatever you need with them.”

好吧, Dinero.js就像Moment一样,只是为了钱 。 没有原生的方式来处理金钱,如果尝试使用Number类型来处理金钱,就会遇到问题。 这就是Dinero.js可以帮助您避免的事情。 它可以确保您的货币价值物有所值,并允许您对它们进行任何处理。”

I was happy with my explanation, as Cory started “a-ha”-ing. But I realized one thing had been missing from the beginning. Something that would speak volumes and help anyone understand the benefits of Dinero.js: a real-world example.

我很高兴自己的解释,因为Cory开始了“ a-ha”的准备 。 但是我意识到从一开始就缺少一件事。 可以说出很多内容并可以帮助任何人理解Dinero.js好处的东西:一个真实的例子

In this tutorial, we’ll build a shopping cart. We’ll use Vue.js to build the component, then integrate Dinero.js to handle all the money stuff.

在本教程中,我们将构建一个购物车 。 我们将使用Vue.js来构建组件,然后集成Dinero.js来处理所有金钱事务。

TL;DR: this post goes in-depth in the how and why. It’s designed to help you grasp the core concepts of Dinero.js. If you want to understand the whole thought process, read on. Otherwise you can look at the final code on CodeSandbox.

TL; DR: 这篇文章深入探讨了如何以及为什么。 它旨在帮助您掌握Dinero.js的核心概念。 如果您想了解整个思考过程,请继续阅读。 否则,您可以在CodeSandbox上查看最终代码。

This post assumes you have basic knowledge of Vue.js. If not, first check my tutorial “Build Your First Vue.js Component.” It will equip you with everything you need to go further.

这篇文章假定您具有Vue.js的基本知识。 如果没有,请首先检查我的教程“构建第一个Vue.js组件” 。 它会为您提供一切所需的一切。

入门 (Getting started)

For this project, we’ll use vue-cli and the webpack-simple Vue.js template. If you don’t have vue-cli installed globally on your machine, fire up your terminal and type the following:

对于此项目,我们将使用vue-cli简单的 webpack Vue.js模板。 如果您的计算机上未全局安装vue-cli,请启动终端并输入以下内容:

npm install -g vue-cli

Then:

然后:

vue init webpack-simple path/to/my-project

You can keep the default options for all questions. When it’s done, navigate to the new directory, install dependencies, and run the project:

您可以保留所有问题的默认选项。 完成后,导航到新目录,安装依赖项并运行项目:

cd path/to/my-project npm install npm run dev

Webpack will start serving your project on port 8080 (if available) and open it in your browser.

Webpack将开始在端口8080 (如果有)上为您的项目提供服务,并在浏览器中打开它。

设置HTML / CSS (Setting up the HTML/CSS)

I won’t get into page structure and styling in this tutorial, so I invite you to copy/paste the code. Open the App.vue file, and paste the following snippets.

在本教程中我不会介绍页面结构和样式 ,因此我邀请您复制/粘贴代码。 打开App.vue文件,然后粘贴以下代码片段。

This goes between the <template> tags:

这介于<templa te>标记之间:

<div id="app">  <div class="cart">    <h1 class="title">Order</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 class="cart-line">      Subtotal <span class="cart-price"></span>    </h3>    <h3 class="cart-line">      Shipping <span class="cart-price"></span>    </h3>    <h3 class="cart-line">      Total <span class="cart-price cart-total"></span>    </h3>  </div></div>

Ant this between the <style> tags:

<sty le>标签之间使用此方法:

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;}

新增资料 (Adding data)

When you’re dealing with products, you usually retrieve raw data from a database or an API. We can get close by representing it in a separate JSON file, then importing it asynchronously as if we were querying an API.

在处理产品时,通常会从数据库或API中检索原始数据。 我们可以通过在单独的JSON文件中表示它,然后像在查询API一样异步导入它来接近它。

Let’s create a products.json file in assets/ and add the following:

让我们在assets/创建一个products.json文件,并添加以下内容:

{  "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}

This is pretty similar to what we would get from a real API: data as a collection, with titles and text as strings, and quantity and prices as numbers.

这与我们从真实的API中获得的结果非常相似:数据为集合,标题和文本为字符串,数量和价格为数字。

We can go back to App.vue and set empty values in data. This will allow the template to initialize while the actual data is being fetched.

我们可以返回App.vue并在data设置空值。 这将允许模板在获取实际数据时进行初始化。

data() {  return {    data: {      items: [],      shippingPrice: 0    }  }}

Finally, we can fetch data from products.json with an asynchronous request, and update the data property when it’s ready:

最后,我们可以使用异步请求从products.json获取数据,并在就绪后更新data属性:

export default {  ...  created() {    fetch('./src/assets/products.json')      .then(response => response.json())      .then(json => (this.data = json))  }}

Now let’s populate our template with this data:

现在,让我们用以下数据填充模板:

<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>...

You should see all the items in your cart. Now let’s add some computed properties to calculate the subtotal and total:

您应该看到购物车中的所有物品。 现在,让我们添加一些计算属性以计算小计和总计:

export default {  ...  computed: {    getSubtotal() {      return this.data.items.reduce(        (a, b) => a + b.price * b.quantity,        0      )    },    getTotal() {      return (        this.getSubtotal + this.data.shippingPrice      )    }  }}

And add them to our template:

并将它们添加到我们的模板中:

<h3 class="cart-line">  Subtotal  <span class="cart-price">{{ getSubtotal }}</span></h3>...<h3 class="cart-line">  Total  <span class="cart-price cart-total">{{ getTotal }}</span></h3>

There we go! Try changing quantities around — you should see the subtotal and total amounts change accordingly.

好了! 尝试更改数量-您应该会看到小计和总金额的变化。

Now we have a few issues here. First, we’re only showing amounts, not currencies. Sure, we could hard code them in the template right next to the reactive amounts. But what if we want to make a multi-lingual website? Not all languages format money the same way.

现在我们这里有一些问题。 首先,我们只显示金额,而不显示货币。 当然,我们可以在React性金额旁边的模板中对其进行硬编码。 但是,如果我们想建立一个多语言的网站怎么办? 并非所有语言都以相同的方式格式化货币。

What if we want to show all amounts with two decimal places, for better alignment? You could try and keep all initial amounts as floats by using the toFixed method, but then you’d be working with String types which are a lot harder and less performant when it comes to doing math. Also, that would mean changing data for purely presentational purposes, which never is a good idea. What if you need the same data for other purposes and it requires a different format?

如果我们想用两位小数显示所有金额以更好地对齐怎么办? 您可以使用toFixed方法尝试将所有初始金额保持为浮点数,但是随后您将使用String类型,这在进行数学运算时会变得更加困难且性能较低。 同样,这将意味着仅出于演示目的而更改数据,这绝不是一个好主意。 如果您需要相同的数据用于其他目的并且需要不同的格式怎么办?

Finally, the current solution is relying on floating point math, which is a bad idea when it comes to handling money. Try and change a few amounts:

最后,当前的解决方案依赖于浮点运算, 在处理货币时是个坏主意 。 尝试并更改一些数量:

{  "items": [    {      ...      "price": 20.01    },    {      ...      "price": 15.03    },    ...  ]}

Now, look at how broken your shopping cart is ? This isn’t some buggy JavaScript behavior, but a limitation of how we can represent our decimal numbering system with binary machines. If you do math with floats, you’ll sooner or later encounter those inaccuracies.

现在,看看您的购物车有多坏? 这不是JavaScript的错误行为,而是对我们如何用二进制计算机表示十进制数字系统限制。 如果您对浮点数进行数学运算,则迟早会遇到这些错误。

The good news is, we don’t have to use floats to store money. That’s exactly where Dinero.js comes into play.

好消息是, 我们不必使用浮子来存钱 。 这正是Dinero.js发挥作用的地方。

Dinero.js,金钱包装 (Dinero.js, a wrapper for money)

Dinero.js is to money what Moment.js is to dates. It’s a library that lets you create monetary value objects, manipulate them, ask them questions, and format them. It relies on Martin Fowler’s money pattern and helps you solve all common problems caused by floats, primarily by storing amounts in minor currency unit, as integers.

Dinero.js可以赚钱,而Moment.js可以赚钱。 它是一个库,可让您创建货币价值对象 ,对其进行操作,向他们提问,并对它们进行格式化。 它依赖于马丁·福勒(Martin Fowler)的货币模式 ,可以帮助您解决所有由流通量引起的常见问题,主要是通过将次要货币单位存储为整数。

Open up your terminal and install Dinero.js:

打开您的终端并安装Dinero.js:

npm install dinero.js --save

Then import it into App.vue:

然后将其导入App.vue

import Dinero from 'dinero.js'
export default {  ...}

You can now create Dinero objects ?

您现在可以创建Dinero对象了吗?

// returns a Dinero object with an amount of $50Dinero({ amount: 500, currency: 'USD' })
// returns $4,000.00Dinero({ amount: 500 })  .add(Dinero({ amount: 500 }))  .multiply(4)  .toFormat()

Let’s create a factory method to turn our price properties into Dinero objects on demand. We have floats with up to two decimal places. This means if we want to turn them into their equivalents in minor currency units (in our case, dollars), we need to multiply them by 10 to the power of 2.

让我们创建一种工厂方法,以便将price属性按需转换为Dinero对象。 我们的浮点数最多可保留两位小数。 这意味着,如果我们想将它们转换成较小的货币单位(在我们的情况下为美元)的等价物, 我们需要将它们乘以10乘以2的幂

We pass the factor as an argument with a default value, so we can use the method with currencies that have different exponents.

我们将factor作为默认值传递给参数,因此我们可以将方法用于具有不同指数的货币。

export default {  ...  methods: {    toPrice(amount, factor = Math.pow(10, 2)) {      return Dinero({ amount: amount * factor })    }  }}

Dollars are the default currency, so we don’t need to specify it.

美元是默认货币,因此我们不需要指定它。

Because we’re doing floating point math during the conversion, some calculations may end up as slightly inaccurate floats. That’s easy to fix by rounding the result to the closest integer.

由于我们在转换过程中正在进行浮点数学运算,因此某些计算可能最终会导致浮点数稍微不准确。 通过将结果舍入到最接近的整数很容易解决。

toPrice(amount, factor = Math.pow(10, 2)) {  return Dinero({ amount: Math.round(amount * factor) })}

Now we can use toPrice in our computed properties:

现在我们可以在计算属性中使用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)    }  }}

And in our template:

在我们的模板中:

<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>

If you look at your shopping cart, you’ll see {} in place of prices. That’s because we’re trying to display an object. Instead, we need to format them so they can display prices with the right syntax, alongside their currency symbol.

如果您查看购物车,则会看到{}代替价格。 那是因为我们试图显示一个对象。 相反, 我们需要对它们进行格式化,以便它们可以使用正确的语法和货币符号显示价格

We can achieve that with Dinero’s toFormat method.

我们可以使用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>

Look in your browser: you now have a well-formatted, fully functional shopping cart ?

在浏览器中查看: 您现在拥有一个格式正确,功能齐全的购物车

更进一步 (Going further)

Now that you have a good grasp of the basics of Dinero.js, time to raise the bar a little.

既然您已经掌握了Dinero.js的基础知识,那么现在该稍微提高一下标准了。

介绍 (Presentation)

Let’s change shippingPrice to 0 in the JSON file. Your cart should now display “Shipping: $0.00”, which is accurate but not user-friendly. Wouldn’t it be nicer for it to say “Free”?

让我们在JSON文件中将shippingPrice更改为0 。 您的购物车现在应显示“运费:¥ 0.00” ,该信息准确但不友好。 说“免费”会更好吗?

Fortunately, Dinero.js has a plenty of handy methods to ask questions to your instances. In our case, the isZero method is exactly what we need.

幸运的是,Dinero.js有很多方便的方法可以向您的实例提出问题。 在我们的例子中, isZero 方法正是我们所需要的。

In the template, you can display text instead of a formatted Dinero object whenever it represents zero:

在模板中,只要它表示零,就可以显示文本而不是格式化的Dinero对象:

<h3 class="cart-line">  Shipping  <span class="cart-price">    {{      getShippingPrice.isZero() ?      'Free' :      getShippingPrice.setLocale(getLocale).toFormat()    }}  </span></h3>

Of course, you can generalize this behavior by wrapping it in a method. It would take a Dinero object as an argument and return a String. This way, you could show “Free” whenever you try to display a zero amount.

当然,您可以通过将其包装在方法中来概括此行为。 它将Dinero对象作为参数并返回String 。 这样,只要您尝试显示零金额,就可以显示“免费”

语言环境切换 (Locale switching)

Imagine you’re making an e-commerce website. You want to accommodate your international audience, so you translate content and add a language switcher. Yet, there’s one detail that may slip your attention: money formatting also changes depending on the language. For example, €10.00 in American English translates to 10,00 € in French.

想象您正在建立一个电子商务网站。 您想容纳国际观众,因此您可以翻译内容并添加语言切换器。 但是,有一个细节可能会引起您的注意: 货币格式也会根据语言而变化 。 例如,美式英语中的€10.00等于法语中的€10,00。

Dinero.js supports international formatting via the I18n API. This lets you display amounts with localized formatting.

Dinero.js通过I18n API支持国际格式。 这使您可以使用本地化格式显示金额。

Dinero.js is immutable, so we can’t rely on changing Dinero.globalLocale to reformat all existing instances. Instead, we need to use the setLocale method.

Dinero.js是不可变的,因此我们不能依靠更改Dinero.globalLocale来重新格式化所有现有实例。 相反,我们需要使用setLocale 方法

First, we add a new property language in data and set it to a default value. For locales, you need to use a BCP 47 language tag such as en-US.

首先,我们在data添加新的属性language并将其设置为默认值。 对于语言环境,您需要使用BCP 47语言标签,例如en-US

data() {  return {    data: {      ...    },    language: 'en-US'  }}

Now we can use setLocale directly on Dinero objects. When language changes, the formatting will change as well.

现在,我们可以直接在Dinero对象上使用setLocalelanguage更改时,格式也会更改。

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)      )    },    ...  }}

All we need is to add setLocale in toPrice and getSubtotal, the only places where we’re creating Dinero objects.

我们需要做的就是在toPricegetSubtotal添加setLocale ,这是我们创建Dinero对象的唯一位置。

Now we can add our language switcher:

现在,我们可以添加语言切换器:

// 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;}

When you click on the switcher, it will reassign language, which will change how the objects are formatted. Because the library is immutable, this will return new objects instead of changing existing ones. It means if you create a Dinero object and decide to display it somewhere, then reference it somewhere else and apply a setLocale on it, your initial instance won’t be affected. No pesky side effects!

单击切换器时,它将重新分配language ,这将更改对象的格式。 因为库是不可变的,所以它将返回新对象,而不是更改现有对象。 这意味着,如果您创建一个Dinero对象并决定将其显示在某个地方,然后在其他地方引用它并在其上应用setLocale ,则您的初始实例将不会受到影响 。 没有讨厌的副作用!

全部含税 (All tax included)

It’s common to see a tax line on shopping carts. You can add one with Dinero.js, using the percentage method.

在购物车上看到税线很常见。 您可以添加一个与Dinero.js,采用percentage

First, let’s add a vatRate property in the JSON file:

首先,让我们在JSON文件中添加vatRate属性:

{  ...  "vatRate": 20}

And an initial value in data:

data的初始值:

data() {  return {    data: {      ...      vatRate: 0    }  }}

Now we can use this value to calculate the total of our cart with tax. First, we need to create a getTaxAmount computed property. We can then add it to getTotal as well.

现在,我们可以使用此值来计算带税的购物车总额。 首先,我们需要创建一个getTaxAmount计算属性。 然后,我们也可以将其添加到getTotal中。

export default {  ...  computed: {    getTaxAmount() {      return this.getSubtotal.percentage(this.data.vatRate)    },    getTotal() {      return this.getSubtotal        .add(this.getTaxAmount)        .add(this.getShippingPrice)    }  }}

The shopping cart now shows the total with tax. We can also add a line to show what the tax amount is:

现在,购物车会显示含税总额。 我们还可以添加一行以显示税额是多少:

<h3 class="cart-line">  VAT ({{ data.vatRate }}%)  <span class="cart-price">{{ getTaxAmount.toFormat() }}</span></h3>

And we’re done! We’ve explored several concepts of Dinero.js, but that’s only scratching the surface of what it has to offer. You can read through the documentation and check out the project on GitHub. Star it, fork it, send me feedback, or even open a pull request! I have a nice little contributing guide to help you get started.

我们完成了! 我们已经探究了Dinero.js的几个概念,但这仅仅是其提供的内容的起点。 您可以阅读文档并在GitHub上检出项目。 为它加注星标,对其进行分叉,向我发送反馈,甚至打开请求请求! 我有一个不错的小指南,可以帮助您入门。

You can also look at the final code on CodeSandbox.

您也可以在CodeSandbox上查看最终代码。

I’m currently working on bringing a convert method to Dinero.js, as well as better support for all ISO 4217 currencies and cryptos. You can stay tuned by following me on Twitter.

我目前正在努力将convert方法引入Dinero.js,以及更好地支持所有ISO 4217货币和加密货币 。 您可以在Twitter上关注我,以保持关注。

Happy coding! ??‍?

编码愉快! ??

Originally published at frontstuff.io.

最初发布在frontstuff.io上

翻译自: https://www.freecodecamp.org/news/how-to-build-a-shopping-cart-with-vue-and-dinero-js-22a7dc4c5352/

vue.js购物车计算

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值