Demo checkout page via Vuejs

by Gaoping

HTML

<script src="https://unpkg.com/vue"></script>
  <h1>Demo Checkout page via Vuejs</h1>

  <div id="app">
    <div class="main">
      <div class="step">
        <div class="number">1</div>
        <div class="name">Shipping address</div>
        <div class="content">
          <div v-if="editAddress">
            <label>Address:</label> <br>
            <input type="text" v-model="address"> <br>
            <label>State:</label> <br>
            <input type="text" v-model="state"> <br>
            <label>Zipcode:</label> <br>
            <input type="text" v-model="zipcode"> <br>
          </div>
          <div v-else>
            <!-- add some filters -->
            {{address | uppercase}} <br>
            {{state | uppercase}} {{zipcode}}, USA
          </div>

          <button v-if="!editAddress" v-on:click="editAddress = !editAddress">Change</button>
          <button v-else v-on:click="editAddress = !editAddress">Save</button>
        </div>
      </div>
      <div class="step">
        <div class="number">2</div>
        <div class="name">Payment method</div>
        <div class="content">
          <div v-if="editPayment">
            <label>Card Number:</label> <br>
            <input type="text" v-model="cardnumber"> <br>            
          </div>
          <div v-else>VISA ending in {{cardnumber | last4digit}}</div>
          <strong>Billing address:</strong> {{billingAddress | uppercase}}
          <button v-if="!editPayment" v-on:click="editPayment = !editPayment">Change</button>
          <button v-else v-on:click="editPayment = !editPayment">Save</button>
        </div>
      </div>
      <div class="step">
        <div class="number">3</div>
        <div class="name">Review items</div>
        <div style="clear: both"></div>
        <div class="content-lg">
          <!-- v-for and components -->
          <!-- pass properties to components -->
          <order-item v-for="item of items" :key="item.id" :item="item" v-on:decrease="decreaseNumber"...

CSS

body {
  font-family: 'Helvetica Neue', Arial, sans-serif;
  font-size: 18px;
  margin: 10px;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #34495e;
  min-width: 800px;
}

body a {
  color: #42b983;
  font-weight: 600;
}

pre {
  margin: 0px;
}

pre.code-area {
  margin-top: 10px;
  margin-bottom: -70px;
}

ol li {
  padding: 5px 0px;
}

input {
  font-family: Helvetica;
  font-size: 20px;
  padding-left: 5px;
}

h1 {
  color: #483d8b;
  border-bottom: 2px solid #aaa;
  padding-bottom: 15px;
}

.page-header {
  border-bottom: 1px solid #ccc;
  padding-bottom: 15px;
  padding-top: 30px;
}

.counter-group button {
  font-size: 18px;
  height: 29px;
  width: 29px;
}
.counter-group input {
  width: 30px;
}

.main {
  float: left;
  width: 69%;
}
.main .step {
  padding-bottom: 10px;
  padding-top: 10px;
  border-bottom: solid 1px #ddd;
}
.main .step:after {
  content: '';
  display: block;
  clear: both;
}
.main .step:last-child {
  border: none;
}
.main .number, .main .name {
  font-weight: bold;
  float: left;
}
.main .number {
  width: 5%;
}
.main .name {
  width: 30%;
}
.main .content {
  float: left;
  width: 65%;
  position: relative;
  font-family: serif;
}
.main .content input {
  font-size: 14px;
}
.main .content button {
  padding: 1px 20px;
  position: absolute;
  right: 0;
  top: 0;
}
.main .content-lg {
  margin-top: 10px;
  margin-left: 20px;
  border: 1px solid #ddd;
  padding: 10px;
}

.item-body {
  display: block;
  font-family: Arial !important;
  font-size: 14px;
}
.item-body:after {
  content: '';
  display: block;
  clear: both;
}
.item-body h4, .item-body p {
  padding-top: 5px;
  margin: 0px;
}
.item-body .image {
  float: left;
}
.item-body .seller {
  color: #888;
}
.item-body .price {
  color: #a82f0b;
  font-weight: bold;
}
.item-body .item-content {
  float: left;
  margin-left: 20px;
}
.item-body .quantity-group {
  display: inline-block;
}

.total {
  margin-top: 10px;
  margin-left: 20px;
 ...

Vue

// register some filters
Vue.filter('uppercase', function(value) {
  return value.toUpperCase()
})

Vue.filter('last4digit', function(value) {
  return value.slice(value.length-4)
})


// register components
Vue.component('order-item', {
  template: '#item-template',  // include template from <script type="x-template">
  props: ['item'],  // properties passed from parent
  methods: {
    decrease: function() {
      this.$emit('decrease', this.item.id)
    },
    increase: function() {
      this.$emit('increase', this.item.id)
    }
  }
})

Vue.component('my-sidebar', {
  template: '#sidebar-template',  // include template from <script type="x-template">
  props: ['items', 'totalCostWithTax', 'tax', 'totalCostNoTax'],
  computed: {
    itemNumber: function() {
      var res = 0
      this.items.forEach(function(item) {
        res += item.number
      })
      return res
    }
  }
})

// create a root vue instance
new Vue({
  el: '#app',
  data: {
    items: [{
      id: 1,
      name: 'Wireless mouse',
      imgUrl: 'https://gaopinghuang0.github.io/assets/imgs/mouse.jpg',
      seller: 'Amazon.com LLC',
      price: 9.99,
      number: 2
    }, {
      id: 2,
      name: 'Wireless keyboard',
      imgUrl: 'https://gaopinghuang0.github.io/assets/imgs/keyboard.png',
      seller: 'Amazon.com LLC',
      price: 19.99,
      number: 1
    }],
    zipcode: 47906,
    state: 'IN',
    address: 'SOME ADDRESS',
    editAddress: false,
    editPayment: false,
    cardnumber: '000000000001234'
  },
  computed: {
    billingAddress: function() {
      return this.address + ', ' + this.state + ' ' + this.zipcode + ', USA'
    },
    totalCostWithTax: function() {
      return (Number(this.totalCostNoTax) + Number(this.tax)).toFixed(2)
    },
    tax: function() {
      return (this.totalCostNoTax * 0.07).toFixed(2)
    },
    totalCostNoTax: function() {
      var total = 0
      this.items.forEach(function(item) {
        total += item.price * item.number
      })
     ...