ali 1 éve
szülő
commit
7656026842

+ 34 - 1
src/api/models/Product.js

@@ -7,7 +7,8 @@ export default class ProductAPI extends APIRepository {
     super('product', appApi)
     this.APIAdresses = {
       base: '/store/products',
-      byId: (id) => '/store/products/' + id
+      byId: (id) => '/store/products/' + id,
+      relatedProducts: (id) => '/store/products/' + id + '/related_products'
     }
     this.CacheList = {
       base: this.name + this.APIAdresses.base
@@ -69,6 +70,38 @@ export default class ProductAPI extends APIRepository {
     })
   }
 
+  relatedProducts(data) {
+    return this.sendRequest({
+      apiMethod: 'get',
+      api: this.api,
+      request: this.APIAdresses.relatedProducts(data.id),
+      data: this.getNormalizedSendData({
+        per_page: 10, // Number
+        page: 1 // Number
+      }, data),
+      resolveCallback: (response) => {
+        const paginate = response.data
+        const results = response.data.results
+        delete paginate.results
+        return {
+          list: new ProductList(results),
+          paginate
+          // {
+          //   "count": 1,
+          //   "num_pages": 1,
+          //   "per_page": 10,
+          //   "current": 1,
+          //   "next": null,
+          //   "previous": null,
+          // }
+        }
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
   add (data) {
     return this.sendRequest({
       apiMethod: 'post',

+ 4 - 4
src/components/Widgets/Store/StoreProduct/StoreProductShow/StoreProductShow.vue

@@ -82,7 +82,7 @@
       <div class="product-show__descriptions-data"
            v-html="product.description" />
     </div>
-    <div v-if="product.medias_info"
+    <div v-if="product.medias_info.length > 0"
          class="product-show__medias">
       <medias-component :medias="product.medias_info" />
     </div>
@@ -99,7 +99,7 @@ import DiscountBadge from './components/discountBadge.vue'
 import Breadcrumbs from 'src/components/Widgets/Breadcrumbs/Breadcrumbs.vue'
 
 export default {
-  name: 'ContentShow',
+  name: 'StoreProductShow',
   components: { Breadcrumbs, MediasComponent, DiscountBadge },
   mixins: [mixinWidget],
   data () {
@@ -115,7 +115,7 @@ export default {
   },
   mounted() {
     this.setBreadcrumbsLoading()
-    this.getPackage()
+    this.getProduct()
   },
   methods: {
     addToCart () {
@@ -136,7 +136,7 @@ export default {
           this.basket.loading = false
         })
     },
-    getPackage () {
+    getProduct () {
       this.product.loading = true
       APIGateway.product.get(this.productId)
         .then((product) => {

+ 21 - 0
src/components/Widgets/Store/StoreProduct/StoreRelatedProducts/OptionPanel.vue

@@ -0,0 +1,21 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      ...
+    </template>
+  </option-panel-tabs>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
+import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
+
+export default defineComponent({
+  name: 'OptionPanel',
+  components: {
+    OptionPanelTabs
+  },
+  mixins: [mixinOptionPanel]
+})
+</script>

+ 133 - 0
src/components/Widgets/Store/StoreProduct/StoreRelatedProducts/StoreRelatedProducts.vue

@@ -0,0 +1,133 @@
+<template>
+  <div class="StoreRelatedProducts"
+       :style="localOptions.style">
+    <q-card v-if="relatedProducts.list.length > 0"
+            class="StoreRelatedProducts__card">
+      <q-card-section>
+        محصولات مرتبط
+      </q-card-section>
+      <q-separator />
+      <div class="products">
+        <product-item v-for="(product, productIndex) in relatedProducts.list"
+                      :key="productIndex"
+                      :product="product"
+                      :add-to-cart-loading="addToCartLoading"
+                      class="product-item"
+                      @add-to-cart="addToCart" />
+      </div>
+    </q-card>
+  </div>
+</template>
+
+<script>
+import { Basket } from 'src/models/Basket.js'
+import { mixinWidget } from 'src/mixin/Mixins.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import ProductItem from './components/productItem.vue'
+import { Product, ProductList } from 'src/models/Product.js'
+
+export default {
+  name: 'StoreRelatedProducts',
+  components: { ProductItem },
+  mixins: [mixinWidget],
+  data () {
+    return {
+      addToCartLoading: false,
+      relatedProducts: new ProductList()
+    }
+  },
+  computed: {
+    productId () {
+      return this.$route.params.id
+    }
+  },
+  mounted () {
+    this.getRelatedProducts()
+  },
+  methods: {
+    getRelatedProducts () {
+      this.relatedProducts.loading = true
+      APIGateway.product.relatedProducts({ id: this.productId })
+        .then((products) => {
+          this.relatedProducts = new ProductList(products.list)
+        })
+        .finally(() => {
+          this.relatedProducts.loading = false
+        })
+    },
+    addToCart (productId) {
+      this.addToCartLoading = true
+      APIGateway.basketItem.addProduct(productId, 1)
+        .finally(() => {
+          this.addToCartLoading = false
+          this.checkoutReview()
+        })
+    },
+    checkoutReview () {
+      this.basket.loading = true
+      APIGateway.basket.checkoutReview()
+        .then((basket) => {
+          this.$store.commit('Shop/updateBasket', new Basket(basket))
+        })
+        .finally(() => {
+          this.basket.loading = false
+        })
+    },
+    getProduct () {
+      this.product.loading = true
+      APIGateway.product.get(this.productId)
+        .then((product) => {
+          this.product = new Product(product)
+          this.updateBreadcrumbs()
+        })
+        .finally(() => {
+          this.product.loading = false
+        })
+    },
+    setBreadcrumbsLoading () {
+      this.$store.commit('AppLayout/updateBreadcrumbs', {
+        visible: false,
+        loading: true
+      })
+    },
+    updateBreadcrumbs () {
+      this.$store.commit('AppLayout/updateBreadcrumbs', {
+        visible: true,
+        loading: false,
+        path: [
+          {
+            label: 'خانه',
+            to: { name: 'Public.Home' }
+          },
+          {
+            label: 'فروشگاه',
+            to: { name: 'Public.Shop' }
+          },
+          {
+            label: this.product.title,
+            to: { name: 'Public.Package', params: { id: this.product } }
+          }
+        ]
+      })
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.StoreRelatedProducts {
+  .StoreRelatedProducts__card {
+    box-shadow: none;
+    border: 1px solid #DFE1EC;
+    border-radius: 8px;
+    .products {
+      .product-item {
+        border-bottom: solid 1px #DFE1EC;
+        &:last-child {
+          border-bottom: none;
+        }
+      }
+    }
+  }
+}
+</style>

+ 188 - 0
src/components/Widgets/Store/StoreProduct/StoreRelatedProducts/components/productItem.vue

@@ -0,0 +1,188 @@
+<template>
+  <div class="product-item">
+    <div class="product-item__meta">
+      <div class="product-item__meta-thumbnail"
+           @click="goToProductPage">
+        <q-img :src="product.thumbnail" />
+      </div>
+      <div class="product-item__meta-info">
+        <div class="product-item__product-title">
+          <router-link :to="{ name: 'Public.Product', params: { id: product.id }}">
+            {{ product.title }}
+          </router-link>
+        </div>
+        <div class="product-item__product-atrs">
+          <div class="product-item__product-atrs-title">
+            ویژگی ها
+          </div>
+          <div class="product-item__product-atrs-list">
+            <div class="product-item__product-atr">
+              <div class="product-item__product-atr-label">
+                {{ product.title }}
+              </div>
+              <div class="product-item__product-atr-value">
+                {{ product.title }}
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="product-item__price">
+      <div class="product-item__price-info">
+        <div class="product-item__price-info-label">
+          <q-icon name="shopping_cart" />
+          قیمت محصول
+        </div>
+        <div class="product-item__price-info-value">
+          <div v-if="product.unit_price !== product.sellable_price"
+               class="product-item__price-base">
+            {{ parseInt(product.unit_price).toLocaleString('fa') }}
+            ریال
+          </div>
+          <div class="product-item__price-final">
+            {{ parseInt(product.sellable_price).toLocaleString('fa') }}
+            ریال
+          </div>
+        </div>
+      </div>
+      <q-btn color="primary"
+             class="product-item__price-action"
+             :loading="addToCartLoading"
+             @click="addToCart">
+        افزودن به سبد
+      </q-btn>
+    </div>
+  </div>
+</template>
+
+<script>
+import { Product } from 'src/models/Product.js'
+
+export default {
+  name: 'productItem',
+  props: {
+    product: {
+      type: Product,
+      default: new Product()
+    },
+    addToCartLoading: {
+      type: Boolean,
+      default: false
+    }
+  },
+  emits: ['add-to-cart'],
+  methods: {
+    goToProductPage () {
+      this.$router.push({ name: 'Public.Product', params: { id: this.product.id } })
+    },
+    addToCart () {
+      this.$emit('add-to-cart', this.product.id)
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.product-item {
+  padding: 24px;
+  display: flex;
+  justify-content: space-between;
+  background: #F9F9FB;
+  .product-item__meta {
+    display: flex;
+    gap: 32px;
+    justify-content: flex-start;
+    .product-item__meta-thumbnail {
+      cursor: pointer;
+      width: 123px;
+    }
+    .product-item__meta-info {
+      .product-item__product-title {
+        color: #212121;
+        font-size: 18px;
+        margin-bottom: 24px;
+      }
+      .product-item__product-atrs {
+        .product-item__product-atrs-title {
+          font-size: 16px;
+          letter-spacing: 0;
+          color: #AAA095;
+          margin-bottom: 19px;
+        }
+        .product-item__product-atrs-list {
+          .product-item__product-atr {
+            font-size: 16px;
+            display: flex;
+            gap: 4px;
+            align-items: center;
+            margin-bottom: 31px;
+            justify-content: flex-start;
+            &:before {
+              content: ' ';
+              width: 8px;
+              height: 8px;
+              border-radius: 100%;
+              background: #475F4A;
+              margin-right: 6px;
+            }
+            .product-item__product-atr-label {
+              font-size: 16px;
+              color: #797979;
+            }
+            .product-item__product-atr-value {
+              font-size: 16px;
+              color: #212121;
+            }
+          }
+        }
+      }
+    }
+  }
+  .product-item__price {
+    display: flex;
+    flex-direction: column;
+    gap: 29px;
+    justify-content: center;
+    align-items: center;
+    .product-item__price-info {
+      display: flex;
+      flex-flow: column;
+      justify-content: center;
+      align-items: flex-end;
+      .product-item__price-info-label {
+        font-size: 16px;
+        color: #616161;
+        margin-right: 53px;
+        margin-bottom: 20px;
+        .q-icon {
+          font-size: 20px;
+          color: #AAA095;
+          margin-right: 10px;
+        }
+      }
+      .product-item__price-info-value {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        padding: 0 20px;
+        //margin-bottom: 43px;
+        margin-bottom: 8px;
+        .product-item__price-final {
+          font-size: 24px;
+          color: #475F4A;
+        }
+        .product-item__price-base {
+          font-size: 16px;
+          color: #FF3D3D;
+          text-decoration: line-through;
+          margin-right: 18px;
+        }
+      }
+    }
+    .product-item__price-action {
+      width: 252px;
+    }
+  }
+}
+</style>