1
0

4 Incheckningar d734198424 ... 2e04f663de

Upphovsman SHA1 Meddelande Datum
  kiboky 2e04f663de 상품 삭제 mutation 추가 5 år sedan
  kiboky bb5a5c3e5d 수량 변경 mutation 적용 5 år sedan
  kiboky 89b77e4a7f 로컬스토리지로 vuex 상태 유지 (Persist Vuex in localStorage), 5 år sedan
  kiboky c2e9af7832 vuex 적용, state, action, mutations, computed, ...mapGetters 5 år sedan

+ 5 - 1
client/components/Navbar.vue

@@ -111,7 +111,7 @@
                 <span aria-hidden="true" class="nav-line-1"></span>
                 <span aria-hidden="true" class="nav-line-2">Cart</span>
                 <span class="nav-cart-icon nav-sprite"></span>
-                <span aria-hidden="true" class="nav-cart-count nav-cart-0">0</span>
+                <span aria-hidden="true" class="nav-cart-count nav-cart-0">{{ getCartLength }}</span>
               </nuxt-link>
             </div>
           </div>
@@ -125,10 +125,14 @@
 </template>
 
 <script>
+import { mapGetters } from  'vuex'
 import Search from '~/components/Search'
 export default {
   components: {
     Search
+  },
+  computed: {
+    ...mapGetters(['getCartLength'])
   }
 }
 </script>

+ 1 - 0
client/nuxt.config.js

@@ -32,6 +32,7 @@ export default {
   ** Plugins to load before mounting the App
   */
   plugins: [
+    { src: '~/plugins/localStorage.js', ssr: false }
   ],
   /*
   ** Nuxt.js dev-modules

+ 14 - 0
client/package-lock.json

@@ -7437,6 +7437,11 @@
       "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz",
       "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg=="
     },
+    "shvl": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/shvl/-/shvl-2.0.0.tgz",
+      "integrity": "sha512-WbpzSvI5XgVGJ3A4ySGe8hBxj0JgJktfnoLhhJmvITDdK21WPVWwgG8GPlYEh4xqdti3Ff7PJ5G0QrRAjNS0Ig=="
+    },
     "signal-exit": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
@@ -8612,6 +8617,15 @@
       "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.1.2.tgz",
       "integrity": "sha512-ha3jNLJqNhhrAemDXcmMJMKf1Zu4sybMPr9KxJIuOpVcsDQlTBYLLladav2U+g1AvdYDG5Gs0xBTb0M5pXXYFQ=="
     },
+    "vuex-persistedstate": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/vuex-persistedstate/-/vuex-persistedstate-2.7.0.tgz",
+      "integrity": "sha512-mpko65DUMBY4mF4sSGsgrqjE7fwO373LFZeuNrC55glRuBBAK4dkgzjr4j4Bij7WtMoKuo2t2w0NGenjauISaQ==",
+      "requires": {
+        "deepmerge": "^4.2.2",
+        "shvl": "^2.0.0"
+      }
+    },
     "watchpack": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz",

+ 2 - 1
client/package.json

@@ -17,7 +17,8 @@
     "bootstrap": "^4.1.3",
     "bootstrap-vue": "^2.0.0",
     "nuxt": "^2.0.0",
-    "vue-star-rating": "^1.6.1"
+    "vue-star-rating": "^1.6.1",
+    "vuex-persistedstate": "^2.7.0"
   },
   "devDependencies": {}
 }

+ 220 - 0
client/pages/cart.vue

@@ -0,0 +1,220 @@
+<template>
+  <!--MAIN-->
+  <main>
+    <!--SHOPPING CART-->
+    <div class="shopping-cart mt-3">
+      <div class="container-fluid c-section">
+        <div class="row">
+          <div class="col-lg-9 col-md-8 col-sm-7">
+            <div class="c-section a-spacing-top-base">
+              <div class="a-row sc-cart-header sc-compact-bottom">
+                <h2>Shopping Cart</h2>
+              </div>
+              <form action="#" method="post">
+                <div class="sc-list-head">
+                  <div class="text-right a-spacing-top-micro">
+                    <span class="a-color-secondary">Price</span>
+                  </div>
+                </div>
+                <!-- List of the item -->
+                <div v-for="product in getCart" :key="product._id" class="sc-list-body">
+                  <div class="sc-list-item-border">
+                    <div class="a-row a-spacing-top-base a-spacing-base">
+                      <div class="row">
+                        <!-- Product's Image -->
+                        <div class="col-sm-2 col-2">
+                          <a href="#" class="a-link-normal">
+                            <img :src="product.photo" class="img-fluid w-100" />
+                          </a>
+                        </div>
+                        <div class="col-sm-8 col-8">
+                          <!-- Product's Title -->
+                          <div class="a-spacing-mini">
+                            <a
+                              href="#"
+                              class="a-link-normal a-size-medium a-text-bold"
+                            >{{ product.title }}</a>
+                            <!-- Product's Owner name -->
+                            <span class="a-size-base sc-product-creator">{{ product.owner.name }}</span>
+                          </div>
+                          <div>
+                            <span
+                              class="a-size-small a-color-secondary sc-product-binding"
+                            >Paperback</span>
+                          </div>
+                          <div>
+                            <span
+                              class="a-size-small a-color-success sc-product-availability"
+                            >In Stock</span>
+                          </div>
+                          <div class="a-checkbox a-align-top a-size-small a-spacing-top-micro">
+                            <label>
+                              <input type="checkbox" name value />
+                              <span class="a-checkbox-label">
+                                This is a gift
+                                <span class="a-size-small">
+                                  <a href="#">
+                                    <span class="a-size-small">Learn More</span>
+                                  </a>
+                                </span>
+                              </span>
+                            </label>
+                          </div>
+                          <div class="sc-action-links">
+                            <select @change="onChangeQuantity($event, product)">
+                              <option v-for=" i in 10"
+                                :key="i"
+                                :value="i"
+                                :selected="checkQty(product.quantity, i)">Qty: &nbsp; {{ i }}</option>
+                            </select>
+                            &nbsp;&nbsp;
+                            <span>|</span>
+                            &nbsp;
+                            <!-- Delete button -->
+                            <span class="a-size-small">
+                              <a @click="$store.commit('removeProduct', product)" href="#">Delete</a>
+                            </span>
+                            &nbsp;
+                            &nbsp;
+                          </div>
+                        </div>
+                        <div class="col-sm-2 col-2 tr sm-txt-r">
+                          <!-- Product's Price -->
+                          <p class="a-spacing-small">
+                            <span
+                              class="a-size-medium a-color-price sc-price sc-white-space-nowrap sc-product-price sc-price-sign a-text-bold"
+                            >${{ product.price * product.quantity }}</span>
+                          </p>
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+                <!-- List of the item -->
+
+                <div class="text-right">
+                  <!-- Cart Subtotal -->
+                  <p class="a-spacing-none a-spacing-top-mini">
+                    <span class="a-size-medium">Subtotal ({{ getCartLength }} item)</span>
+                    <span class="a-color-price a-text-bold">
+                      <!-- Cart Total Price -->
+                      <span class="a-size-medium a-color-price">${{ getCartTotalPrice }}</span>
+                    </span>
+                  </p>
+                </div>
+              </form>
+            </div>
+          </div>
+          <div class="col-lg-3 col-md-4 col-sm-5">
+            <div class="a-box-group" style="margin-bottom: 14px;">
+              <div class="a-box a-color-alternate-background">
+                <div class="a-box-inner">
+                  <div class="a-spacing-mini">
+                    <p class="a-spacing-none a-spacing-top-none">
+                      <!-- Cart Subtotal -->
+                      <span class="a-size-medium">
+                        <span>Subtotal ({{ getCartLength }} item):</span>
+                        <span class="a-color-price a-text-bold">
+                          <!-- Cart Total Price  -->
+                          <span class="a-size-medium a-color-price">${{ getCartTotalPrice }}</span>
+                        </span>
+                      </span>
+                    </p>
+                  </div>
+                  <div class="a-spacing-base mt-1">
+                    <input type="checkbox" name="checkbox" />
+                    <span class="a-label a-checkbox-label">This order contains a gift</span>
+                  </div>
+                  <div>
+                    <span class="a-spacing-small a-button-primary a-button-icon">
+                      <span class="a-button-inner">
+                        <a href="#" class="a-button-text">Proceed to checkout</a>
+                      </span>
+                    </span>
+                  </div>
+                </div>
+              </div>
+            </div>
+
+            <!-- Recently Viewed -->
+            <div class="a-spacing-large">
+              <div class="a-box">
+                <div class="a-box-inner">
+                  <h5 class="a-spacing-base">Your recently viewed items</h5>
+                  <div class="a-spacing-micro">
+                    <ul class="a-unordered-list recently-viewed">
+                      <li class="a-spacing-medium" v-for="i in 4" :key="i">
+                        <span class="a-list-item">
+                          <div class="row">
+                            <div class="col-md-4 col-sm-3 col-3 pl-0">
+                              <a href="#">
+                                <img src="img/cartRecent4.png" class />
+                              </a>
+                            </div>
+                            <div class="col-md-8 col-sm-9 col-9">
+                              <a href="#" class="a-link-normal">The Everything Store:…</a>
+                              <div class="a-size-small">
+                                <a href="#" class="a-size-small a-link-child">Brad Stone</a>
+                              </div>
+                              <div class="a-icon-row a-spacing-none">
+                                <a href="#">
+                                  <i class="fas fa-star"></i>
+                                  <i class="fas fa-star"></i>
+                                  <i class="fas fa-star"></i>
+                                  <i class="fas fa-star"></i>
+                                  <i class="fas fa-star-half-alt"></i>
+                                </a>
+                                <a href="#">155</a>
+                              </div>
+                              <div class="a-size-small">
+                                <span class="a-size-small a-color-secondary">Kindle Edition</span>
+                              </div>
+                              <div class="a-spacing-top-micro">
+                                <span
+                                  class="a-button-inspired a-spacing-top-none a-button-base a-button-small"
+                                >
+                                  <span class="a-button-inner">
+                                    <a href="#" class="a-button-text">See all buying options</a>
+                                  </span>
+                                </span>
+                              </div>
+                            </div>
+                          </div>
+                        </span>
+                      </li>
+                    </ul>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <!--/SHOPPING CART-->
+  </main>
+  <!--/MAIN-->
+</template>
+
+<script>
+import { mapGetters } from  'vuex'
+export default {
+
+  computed: {
+    ...mapGetters(['getCart', 'getCartTotalPrice', 'getCartLength'])
+  },
+  methods: {
+    onChangeQuantity (event, product) {
+      let qty = event.target.value * 1
+      this.$store.commit('changeQty', { product, qty })
+    },
+    checkQty (prodQty, qty) {
+      if (prodQty * 1  === qty * 1) {
+        return true
+      } else {
+        return false
+      }
+    }
+  }
+}
+</script>

+ 5 - 1
client/pages/products/_id.vue

@@ -258,7 +258,7 @@
                 </div>
 
                 <div class="a-section">
-                  <div class="a-button-stack">
+                  <div @click="addProductToCart(product)" class="a-button-stack">
                     <span class="a-spacing-small a-button-primary a-button-icon">
                       <span class="a-button-inner">
                         <i class="a-icon a-icon-cart"></i>
@@ -365,6 +365,7 @@
 
 
 <script>
+import { mapActions } from 'vuex'
 import StarRating from 'vue-star-rating'
 import ReviewSection from '~/components/ReviewSection'
 
@@ -389,6 +390,9 @@ export default {
     } catch (err) {
       console.log(err)
     }
+  },
+  methods: {
+    ...mapActions(['addProductToCart'])
   }
 }
 </script>

+ 8 - 0
client/plugins/localStorage.js

@@ -0,0 +1,8 @@
+import createPersistedState from  'vuex-persistedstate'
+
+export default ({ store }) => {
+  window.onNuxtReady(() => {
+    createPersistedState({})(store)
+  })
+}
+

+ 78 - 0
client/store/index.js

@@ -0,0 +1,78 @@
+export const state = () => ({
+  cart: [],
+  cartLength: 0
+})
+
+export const actions = {
+  addProductToCart ({ state, commit }, product) {
+    const cartProduct = state.cart.find(prod => prod._id === product._id)
+
+    if (!cartProduct) {
+      commit('pushProductToCart', product)
+    } else {
+      commit('incrementProductQty', cartProduct)
+    }
+
+    commit('incrementCartLength')
+  }
+}
+
+export const mutations = {
+  pushProductToCart (state, product) {
+    product.quantity = 1
+    state.cart.push(product)
+  },
+
+  incrementProductQty (state, product) {
+    product.quantity++
+    let indexOfProduct = state.cart.indexOf(product)
+    state.cart.splice(indexOfProduct, 1, product)
+  },
+
+  incrementCartLength (state) {
+    state.cartLength = 0
+    if (state.cart.length > 0) {
+      state.cart.map(product => {
+        state.cartLength += product.quantity
+      })
+    }
+  },
+
+  changeQty (state, { product, qty }) {
+    let cartProduct = state.cart.find(prod => prod._id === product._id )
+    cartProduct.quantity = qty
+
+    state.cartLength = 0
+    if (state.cart.length > 0) {
+      state.cart.map(product => {
+        state.cartLength += product.quantity
+      })
+    }
+
+    let indexOfProduct = state.cart.indexOf(cartProduct)
+    state.cart.splice(indexOfProduct, 1, cartProduct)
+  },
+
+  removeProduct (state, product) {
+    state.cartLength -= product.quantity
+    let indexOfProduct = state.cart.indexOf(product)
+    state.cart.splice(indexOfProduct, 1)
+  }
+
+}
+
+export const getters = {
+  getCartLength (state) {
+    return state.cartLength
+  },
+  getCart (state) {
+    return state.cart
+  },
+  getCartTotalPrice (state) {
+    let total = 0
+    state.cart.map(product => {
+      total += product.price * product.quantity
+    })
+    return total
+  }
+}

+ 23 - 1
package-lock.json

@@ -1,3 +1,25 @@
 {
-  "lockfileVersion": 1
+  "requires": true,
+  "lockfileVersion": 1,
+  "dependencies": {
+    "deepmerge": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
+      "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
+    },
+    "shvl": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/shvl/-/shvl-2.0.0.tgz",
+      "integrity": "sha512-WbpzSvI5XgVGJ3A4ySGe8hBxj0JgJktfnoLhhJmvITDdK21WPVWwgG8GPlYEh4xqdti3Ff7PJ5G0QrRAjNS0Ig=="
+    },
+    "vuex-persistedstate": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/vuex-persistedstate/-/vuex-persistedstate-2.7.0.tgz",
+      "integrity": "sha512-mpko65DUMBY4mF4sSGsgrqjE7fwO373LFZeuNrC55glRuBBAK4dkgzjr4j4Bij7WtMoKuo2t2w0NGenjauISaQ==",
+      "requires": {
+        "deepmerge": "^4.2.2",
+        "shvl": "^2.0.0"
+      }
+    }
+  }
 }