Как создать корзину для покупок с помощью Vue и Dinero.js

1656622092 kak sozdat korzinu dlya pokupok s pomoshhyu vue i dinerojs

автор Сара Даян

aZOvofVNEwXYaZxNh9vZn0LwlbpyJGYz-MHT
Фото Аллефа Винисиуса на Unsplash

Мы с моим другом Кори общаемся почти каждый день, так что можно поспорить, что он знает все, что происходит в моей жизни. Но когда мы разговаривали на днях, я понял он понятия не имел, как на самом деле работает Dinero.js, мой последний проект. Мол, что с этим можно поделать.

Я сделал паузу и понял, что это может быть не так что очевиден. Независимо от уровня вашей квалификации, легче понять, что делает плагин плавной прокрутки, чем то, что может предложить библиотека денег.

«Видите ли вы в JavaScript, как можно использовать конструктор Date для сохранения даты и форматирования ее позже? Или вы используете Moment.js для создания объектов момента, и чем лучше, чем сохранять даты в виде строк или любого другого типа?

Ну, Dinero.js похож на Moment, но за деньги. Нет родного способа обращения с деньгами, и если вы попытаетесь это сделать с помощью Number типов, вы столкнетесь с проблемами. Вот что Dinero.js помогает вам избежать. Это защищает ваши денежные ценности в объектах и ​​позволяет делать с ними все, что вам нужно».

Я был доволен своим объяснением, когда Кори начал «а-го»-ing. Но я понял, что изначально мне не хватало одной вещи. То, что может говорить о многом и поможет кому-либо понять преимущества Dinero.js: a реальный пример.

В этом учебнике мы построим a магазинная тележка. Мы будем использовать Vue.js для создания компонента, а затем интегрируем Dinero.js для обработки всех денег.

TL; DR: эта публикация подробно рассказывает о том, как и почему. Он разработан, чтобы помочь вам понять основные концепции Dinero.js. Если вы хотите понять весь процесс мышления, читайте дальше. В противном случае вы можете посмотреть окончательный код CodeSandbox.

Эта публикация предполагает, что у вас есть базовые знания Vue.js. Если нет, сначала просмотрите мой учебник «Создайте свой первый компонент Vue.js». Он обеспечит вас всем необходимым, чтобы идти дальше.

Начинаем

Для этого проекта мы будем использовать vue-cli и шаблон webpack-simple 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">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 это между <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;}

Добавление данных

Когда вы имеете дело с продуктами, вы обычно получаете исходные данные из базы данных или API. Мы можем приблизиться, представив его в отдельном файле JSON, а затем импортируя его асинхронно, как если бы мы спрашивали API.

Давайте создадим а products.json файл в assets/ и добавить следующее:

{  "items": [    {      "title": "Item 1",      "description": "A wonderful product",      "thumbnail": "      "quantity": 1,      "price": 20    },    {      "title": "Item 2",      "description": "A wonderful product",      "thumbnail": "      "quantity": 1,      "price": 15    },    {      "title": "Item 3",      "description": "A wonderful product",      "thumbnail": "      "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=" :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">  Subtotal  <span class="cart-price">{{ getSubtotal }}</span></h3>...<h3 class="cart-line">  Total  <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. Это библиотека, позволяющая создавать объекты денежной стоимости, манипулировать ими, задавать им вопросы и форматировать их. Он опирается на денежный шаблон Мартина Фаулера и помогает вам решать все распространенные проблемы, вызванные плавающими ценами, в первую очередь, сохраняя суммы в мелких денежных единицах в виде целых чисел.

Откройте терминал и установите Dinero.js:

npm install dinero.js --save

Затем импортируйте его в App.vue:

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

Теперь можно создавать объекты 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()

Давайте создадим заводской метод, чтобы превратить наш 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=" :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 к 0 в файле JSON. Теперь ваша корзина должна отобразиться «Доставка: $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 в качестве аргумента и вернет a String. Таким образом, вы могли бы показать «Бесплатно» всякий раз, когда вы пытаетесь отразить нулевую сумму.

Переключение локальных стандартов

Представьте, что вы создаете веб-сайт электронной коммерции. Вы хотите удовлетворить свою международную аудиторию, поэтому переводите содержимое и добавляете переключатель языка. И все же есть одна деталь, которая может обратить ваше внимание: Форматирование денег также меняется в зависимости от языка. Например, 10,00 евро на американском английском означает 10 евро на французском.

Dinero.js поддерживает международное форматирование через I18n API. Это позволяет отображать суммы с локализованным форматированием.

Dinero.js неизменен, поэтому мы не можем полагаться на изменения Dinero.globalLocale переформатировать все имеющиеся экземпляры. Вместо этого нам нужно использовать setLocale метод.

Сначала мы добавляем новое свойство language в data и установить для него значение по умолчанию. Для языковых стандартов необходимо использовать языковой тег BCP 47, например en-US.

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

Теперь мы можем использовать setLocale непосредственно на объектах Dinero. Когда language изменится, изменится и форматирование.

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

Все, что нам нужно, это добавить setLocale в toPrice и getSubtotalединственное место, где мы создаем объекты 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;}

Когда вы нажимаете на переключатель, он переназначается language, что изменит способ форматирования объектов Поскольку библиотека неизменна, это вернет новые объекты вместо того, чтобы изменять существующие. Это означает, что если вы создаете объект Dinero и решите отобразить его где-то, то ссылаетесь на него в другом месте и применяете a setLocale на нем, ваш начальный экземпляр это не повлияет. Никаких неприятных побочных эффектов!

Все налоги включены

Часто можно увидеть налоговую линию на тележках для покупок. Вы можете добавить его с помощью Dinero.js, используя файл percentage метод.

Сначала добавим а vatRate свойство в файле JSON:

{  ...  "vatRate": 20}

И первоначальное значение в data:

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

Теперь мы можем использовать это значение для вычисления общей суммы нашей корзины с налогом. Сначала нам нужно создать a getTaxAmount вычисляемое свойство. Затем мы можем добавить его в 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>

И мы кончили! Мы изучили несколько концепций Dinero.js, но это лишь подкожная поверхность того, что он может предложить. Вы можете прочитать документацию и ознакомиться с проектом GitHub. Отметьте это, разделите его, отправьте мне отзыв или даже откройте запрос на выписку! У меня есть отличное руководство, которое поможет вам начать работу.

Вы также можете посмотреть окончательный код CodeSandbox.

Сейчас я работаю над тем, чтобы привлечь a convert метод для Dinero.js, а также лучшую поддержку всех валют и криптовалют по стандарту ISO 4217. Вы можете следить за обновлениями, подписавшись на меня в Twitter.

Счастливого кодирования! ???‍?

Первоначально опубликовано на frontstuff.io.

Добавить комментарий

Ваш адрес email не будет опубликован.