Платформа электронной коммерции с открытым исходным кодом для Nuxt.js

Введение

Medusa — это безголовый коммерческий движок с открытым исходным кодом на Node.js, который позволяет создавать интернет-магазины через свой API с помощью всего нескольких команд — ссылка на репо. С другой стороны, Nuxt.js — это front-end фреймворк, построенный на базе Vue.js, который включает в себя некоторые функции из коробки, такие как рендеринг сайтов на стороне сервера, статические сайты, маршрутизация файловой системы, выборка данных, мета-теги, SEO и многое другое.

В этом руководстве вы узнаете, как создать начальную витрину магазина с Nuxt.js для фронтенда и связать ее с сервером Medusa. Для этого сначала вы создадите проект Nuxt.js и установите несколько простых компонентов, страниц и макета. Затем вы свяжете проект Nuxt.js с сервером Medusa, чтобы получить некоторые продукты из API и отобразить их на главной странице, странице продукта и странице с подробной информацией о продукте.

Окончательный код можно найти в этом репозитории GitHub.

Если у вас возникнут какие-либо проблемы в процессе настройки, пожалуйста, свяжитесь с нами в Medusa Discord.

Предварительные условия

Чтобы следовать этому руководству, вам потребуется следующее:

  • Node.js, рекомендуется установить последнюю версию LTS.
  • Установлен один из следующих менеджеров пакетов: npm, yarn, или npx (включен по умолчанию в npm v5.2+).
  • На сервер Medusa загружаются фиктивные данные для работы, поэтому, если это не так, пожалуйста, сначала прочитайте руководство по быстрому запуску для установки сервера Medusa, а затем вернитесь к продолжению.

Настройка витрины магазина

⚠️ В этом руководстве используется Nuxt v2.15.8, так как на момент написания статьи существует две версии Nuxt, но более новая (Nuxt v3) пока находится в состоянии бета-версии.

Установка проекта Nuxt.js

Чтобы установить проект Nuxt, вы можете быстро начать с create-nuxt-app. Откройте терминал и выполните следующую команду

// npx create-nuxt-app <project-name>
npx create-nuxt-app nuxtjs-storefront
Войти в полноэкранный режим Выйти из полноэкранного режима

Программа задаст вам несколько вопросов. Вы можете выбрать опции, которые лучше всего подходят для вашего рабочего процесса разработки, но для простоты этого руководства я рекомендую установить проект, используя следующие опции:

Запустить проект Nuxt.js

После того как проект Nuxt.js будет создан, перейдите в каталог витрины магазина

cd nuxtjs-storefront
Войдите в полноэкранный режим Выйти из полноэкранного режима

Затем выполните следующую команду

yarn dev
Войти в полноэкранный режим Выйти из полноэкранного режима

Эта команда запустит приложение магазина по умолчанию по адресу http://localhost:3000. Чтобы проверить это, откройте браузер и перейдите по адресу http://localhost:3000. Вы получите что-то вроде этого:

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

Сделайте макет витрины магазина

Прежде чем приступить к подключению сервера Medusa к витрине, необходимо добавить некоторые компоненты и страницы в витрину. Откройте проект витрины в выбранной вами IDE.

Вы должны увидеть следующие директории:

Вы сосредоточитесь в основном на директориях components и pages для разработки макета витрины.

Компоненты

Компоненты — это то, что составляет различные части вашей страницы. Их можно повторно использовать и импортировать в страницы, макеты и даже в другие компоненты.

Создаваемая вами витрина будет иметь следующие компоненты:

  • Логотип
  • Навбар
  • Нижний колонтитул
  • Карточка товара

Перейдите в каталог components и удалите компоненты по умолчанию, которые поставляются с установкой Nuxt.js. Затем добавьте следующие файлы

Логотип → components/App/Logo.vue.

<template>
  <div class="h-16 flex items-center">
    <div class="ml-4 flex lg:ml-0 lg:mr-8">
      <nuxt-link to="/">
        <img class="h-8 w-auto" src="https://i.imgur.com/y3yU55v.png" alt=""/>
      </nuxt-link>
    </div>
  </div>
</template>

<script>
export default {
  name: 'AppLogo'
}
</script>
Вход в полноэкранный режим Выйти из полноэкранного режима

Navbar → components/App/Navbar.vue

<template>
  <div class="sticky top-0 z-20">
    <header class="relative bg-white">
      <nav class="px-4 sm:px-6 lg:px-8 border-b border-ui-medium flex items-center justify-between">
        <div class="flex items-center">
          <app-logo />
          <div class="hidden lg:flex lg:items-center">
            <div class="hidden flex-grow items-center justify-center lg:flex text-sm font-medium">
              <nuxt-link
                to="/"
                class="block mt-4 mr-4 lg:inline-block lg:mt-0 text-gray-700 hover:text-gray-600 last:mr-0"
              >
                Home
              </nuxt-link>
              <nuxt-link
                to="/products"
                class="block mt-4 mr-4 lg:inline-block lg:mt-0 text-gray-700 hover:text-gray-600 last:mr-0"
              >
                Products
              </nuxt-link>
            </div>
          </div>
        </div>

        <div class="flex items-center justify-end">
          <div class="hidden lg:flex">
            <div class="inline-block relative text-left">
              <div>
                <button
                  class="inline-flex justify-center w-full px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:text-gray-600"
                  type="button"
                >
                  USA / USD
                </button>
              </div>
            </div><div class="relative inline-block text-left">
              <div>
                <button
                  class="inline-flex justify-center w-full px-4 py-2 text-sm font-medium text-gray-700 hover:text-gray-600"
                  type="button"
                >
                  Account
                </button>
              </div>
            </div>
          </div><div class="relative inline-block text-left">
            <div>
              <button
                class="inline-flex items-center justify-center w-full py-2 bg-white text-sm font-medium hover:opacity-1/2"
                type="button"
              >
                <svg width="40" height="41" viewBox="0 0 40 41" fill="none" xmlns="http://www.w3.org/2000/svg">
                  <path
                    fill-rule="evenodd"
                    clip-rule="evenodd"
                    d="M14.9968 16.2273C14.9921 16.1189 14.9888 16.0004 14.9877 15.8734C14.9826 15.2497 15.0333 14.4053 15.2648 13.551C15.4962 12.6975 15.9164 11.8043 16.6719 11.123C17.4366 10.4333 18.5016 10 19.9419 10C21.3822 10 22.4472 10.4333 23.212 11.123C23.9674 11.8043 24.3877 12.6975 24.619 13.551C24.8506 14.4053 24.9012 15.2497 24.8961 15.8734C24.8951 16.0004 24.8917 16.1189 24.887 16.2273H27.8836C29.0776 16.2273 30.0056 17.2667 29.8708 18.4531L28.7344 28.4531C28.6196 29.4638 27.7644 30.2273 26.7472 30.2273H13.1366C12.1194 30.2273 11.2643 29.4638 11.1494 28.4531L10.013 18.4531C9.87822 17.2667 10.8062 16.2273 12.0002 16.2273H14.9968ZM23.8859 16.2273C23.8912 16.1186 23.8951 15.9971 23.8962 15.8652C23.9008 15.2957 23.8535 14.5493 23.6538 13.8126C23.454 13.0752 23.1098 12.3775 22.5422 11.8656C21.984 11.3622 21.1673 11 19.9419 11C18.7165 11 17.8999 11.3622 17.3416 11.8656C16.774 12.3775 16.4299 13.0752 16.23 13.8126C16.0303 14.5493 15.983 15.2957 15.9877 15.8652C15.9888 15.9971 15.9926 16.1186 15.9979 16.2273H23.8859ZM12.0002 17.2273H27.8836C28.4806 17.2273 28.9446 17.747 28.8772 18.3402L27.7408 28.3402C27.6834 28.8455 27.2558 29.2273 26.7472 29.2273H13.1366C12.628 29.2273 12.2004 28.8455 12.143 28.3402L11.0066 18.3402C10.9392 17.747 11.4032 17.2273 12.0002 17.2273ZM15.4874 20.0455C15.8388 20.0455 16.1237 19.7605 16.1237 19.4091C16.1237 19.0576 15.8388 18.7727 15.4874 18.7727C15.1359 18.7727 14.851 19.0576 14.851 19.4091C14.851 19.7605 15.1359 20.0455 15.4874 20.0455ZM25.0328 19.4091C25.0328 19.7605 24.7479 20.0455 24.3965 20.0455C24.045 20.0455 23.7601 19.7605 23.7601 19.4091C23.7601 19.0576 24.045 18.7727 24.3965 18.7727C24.7479 18.7727 25.0328 19.0576 25.0328 19.4091Z"
                    fill="black"
                  /></svg>
                <span>0</span>
              </button>
            </div>
          </div>
        </div>
      </nav>
    </header>
  </div>
</template>

<script>
export default {
  name: 'NavBar'
}
</script>
Войти в полноэкранный режим Выйти из полноэкранного режима

Нижний колонтитул → components/App/Footer.vue

<template>
  <footer>
    <div class="bg-white px-4 pt-24 pb-4 sm:px-6 lg:px-8 border-t border-ui-medium flex items-center justify-between text-sm">
      <div class="flex items-center">
        <a class="mr-3 last:mr-0 text-ui-dark hover:text-gray-700" href="/">Create return</a>
        <a class="mr-3 last:mr-0 text-ui-dark hover:text-gray-700" href="/">FAQ</a>
        <a class="mr-3 last:mr-0 text-ui-dark hover:text-gray-700" href="/">Terms &amp; Conditions</a>
      </div>
      <div class="flex items-center">
        <a href="https://www.github.com/medusajs" class="mr-3 last:mr-0 text-ui-dark hover:text-gray-700">GitHub</a>
        <a href="https://www.twitter.com/medusajs" class="mr-3 last:mr-0 text-ui-dark hover:text-gray-700">Twitter</a>
        <a href="https://discord.gg/ruGn9fmv9q" class="mr-3 last:mr-0 text-ui-dark hover:text-gray-700">Discord</a>
      </div>
    </div>
  </footer>
</template>

<script>
export default {
  name: 'AppFooter'
}
</script>
Войти в полноэкранный режим Выйти из полноэкранного режима

ProductCard → components/ProductCard.vue

<template>
  <div>
    <nuxt-link :to="`/products/${item.id}`">
      <div
        class="group relative"
      >
        <div class="w-full min-h-auto bg-gray-200 aspect-w-1 aspect-h-1 rounded-md overflow-hidden group-hover:opacity-75 lg:h-80 lg:aspect-none">
          <div class="w-auto h-full object-center object-cover bg-gray-100">
            <img
              alt=""
              :src="item.thumbnail"
            >
          </div>
        </div>
        <div class="mt-4 flex justify-between">
          <h3 class="text-sm text-gray-700 font-normal">
            {{ item.title }}
          </h3>
          <p class="text-sm font-semibold text-gray-900">
            from {{ lowestPrice.amount/100 }} {{ lowestPrice.currency_code.toUpperCase() }}
          </p>
        </div>
      </div>
    </nuxt-link>
  </div>
</template>

<script>
export default {
  name: 'ProductCard',
  props: {
    item: {
      type: Object,
      default () {
        return {
          id: 1,
          title: 'Kitchen Table',
          thumbnail: 'https://picsum.photos/600/600',
          variants: [{ prices: [{ amount: 0 }] }]
        }
      }
    }
  },
  computed: {
    lowestPrice () {
      const lowestPrice = this.item.variants.reduce((acc, curr) => {
        return curr.prices.reduce((lowest, current) => {
          if (lowest.amount > current.amount) {
            return current
          }
          return lowest
        })
      }, { amount: 0 })

      return lowestPrice || { amount: 10, currency_code: 'usd' }
    }
  }
}
</script>
Войти в полноэкранный режим Выход из полноэкранного режима

Обратите особое внимание на компоненты Logo, Navbar и Footer. Они должны находиться в папке App.

Pages

Каталог pages содержит представления и маршруты вашей витрины. Для этого руководства вам понадобится только 3 страницы:

  • Главная страница
  • страница товаров
  • Страница с подробной информацией о товаре

В директории pages откройте файл index.vue и замените уже имеющийся там код на следующий

Index → /pages/index.vue

    <template>
      <div>
        <div class="bg-ui-light pb-12 lg:pb-0 w-full px-4 sm:px-6 lg:px-12">
          <div class="flex flex-col lg:flex-row items-center max-w-screen-2xl mx-auto">
            <div class="w-auto h-full object-center object-cover p-12">
              <img
                width="600"
                alt=""
                src="https://start.medusajs.com/static/9803c162c71fd1960d9d11253859c701/246b5/hero-merch.webp"
              >
            </div>
            <div>
              <h1 class="text-4xl">
                CLAIM YOUR MERCH
              </h1>
              <p class="mt-2 text-lg font-normal">
                Contribute to Medusa and receive free merch<br>as a token of our appreciation
              </p>
              <button class="btn-ui mt-4 min-w-full lg:min-w-0">
                Learn more
              </button>
            </div>
          </div>
        </div>

        <div
          v-if="products.length"
          class="container mx-auto px-8 py-16"
        >
          <div class="flex items-center justify-between mb-6">
            <p class="text-2xl font-semibold text-gray-700">
              Featured
            </p>
            <nuxt-link
              class="text-ui-dark flex items-center"
              to="/products"
            >
              <span class="mr-2 text-ui-dark">Browse all products</span>
              <svg
                width="16"
                height="8"
                viewBox="0 0 16 8"
                fill="none"
                xmlns="http://www.w3.org/2000/svg"
              >
                <path d="M15.3536 4.35355C15.5488 4.15829 15.5488 3.84171 15.3536 3.64645L12.1716 0.464466C11.9763 0.269204 11.6597 0.269204 11.4645 0.464466C11.2692 0.659728 11.2692 0.976311 11.4645 1.17157L14.2929 4L11.4645 6.82843C11.2692 7.02369 11.2692 7.34027 11.4645 7.53553C11.6597 7.7308 11.9763 7.7308 12.1716 7.53553L15.3536 4.35355ZM0 4.5H15V3.5H0V4.5Z" fill="#89959C" />
              </svg>
            </nuxt-link>
          </div>
          <div class="grid grid-cols-4 gap-8">
            <ProductCard
              v-for="product in products"
              :key="product.id"
              :item="product"
            />
          </div>
        </div>
      </div>
    </template>

    <script>
    export default {
      name: 'IndexPage',
        data () {
        return {
          products: [{
            id: 1,
            title: 'Kitchen Table',
            thumbnail: 'https://picsum.photos/600/600',
            variants: [{ prices: [{ amount: 0, currency_code: 'usd' }] }]
          }]
        }
      },
    }
    </script>

    <style>
      .btn-ui {
        @apply py-2 px-4 bg-ui-dark text-white text-sm font-medium rounded-md shadow;
        @apply focus:outline-none focus:ring-2 focus:ring-ui-dark focus:ring-opacity-75 disabled:bg-ui-medium;
      }
    </style>
Вход в полноэкранный режим Выйти из полноэкранного режима

Эта страница будет домом для вашей витрины. Она состоит из заголовка-героя и сетки, настроенной на показ только четырех товаров. Единственное, что здесь нужно сделать после подключения витрины к серверу Medusa, это поместить компонент ProductCard в цикл v-for для отображения товаров.

Теперь необходимо создать новую директорию products, которая будет содержать страницу продуктов /pages/products/index.vue и страницу подробного описания продукта /pages/products/_id.vue. Добавьте следующий код на эти страницы.

Страница товаров → /pages/products/index.vue

<template>
  <div class="container mx-auto p-8">
    <div class="w-full border-b border-ui-medium pb-6 mb-2 lg:mb-6 flex items-center justify-between">
      <h1 class="font-semibold text-3xl">
        All Products
      </h1>
    </div>

    <div
      v-if="products.length"
      class="grid grid-cols-4 gap-8 "
    >
      <ProductCard
        v-for="product in products"
        :key="product.id"
        :item="product"
      />
    </div>
  </div>
</template>

<script>
export default {
  name: 'ProductsIndex',
    data () {
    return {
      products: [{
        id: 1,
        title: 'Kitchen Table',
        thumbnail: 'https://picsum.photos/600/600',
        variants: [{ prices: [{ amount: 0, currency_code: 'usd' }] }]
      }]
    }
  },
}
</script>
Войдите в полноэкранный режим Выйти из полноэкранного режима

Эта страница похожа на главную страницу, но без заголовка героя. Здесь отображается сетка со всеми товарами, отправленными сервером Medusa.

Страница подробного описания товара → /pages/products/_id.vue

<template>
  <div class="container mx-auto p-8">
    <div class="flex flex-col lg:flex-row">
      <div class="lg:w-3/5 lg:pr-14">
        <div class="flex">
          <div class="hidden lg:flex flex-col items-center mr-4">
            <div class="w-auto h-full object-center object-cover px-4 space-y-4">
              <img
                v-for="image in product.images"
                :key="image.id"
                width="150"
                alt=""
                :src="image.url"
                class="cursor-pointer"
                @click="imageToShow = image.id"
              >
            </div>
          </div>

          <div class="h-auto w-full flex-1 flex flex-col rounded-lg overflow-hidden">
            <div class="w-auto h-full">
              <div
                v-for="image in product.images"
                :key="image.id"
              >
                <div v-if="image.id === imageToShow">
                  <img
                    alt=""
                    :src="image.url"
                    class=" w-full"
                  >
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>

      <div class="mt-8 lg:mt-0 lg:w-2/5 lg:max-w-xl">
        <h1 class="font-semibold text-3xl">
          {{ product.title }}
        </h1>
        <p v-if="product.variants" class="text-lg mt-2 mb-4">
          {{ product.variants[0].prices[0].amount/100 }} {{ product.variants[0].prices[0].currency_code }}
        </p>
        <p v-else>
          10 USD
        </p>
        <p class="font-light">
          {{ product.description }}
        </p>
        <div v-for="option in options" :key="option.id" class="mt-6">
          <div class="text-sm">
            <p class="font-medium mb-2">
              {{ option.title }}
            </p>
            <div>
              <button
                v-for="value in option.values"
                :key="value.id"
                class="bg-ui-dark text-white inline-flex items-center justify-center rounded-sm text-xs h-12 w-12 mr-2 last:mr-0 hover:bg-ui-dark hover:text-white"
              >
                {{ value.value }}
              </button>
            </div>
          </div>
        </div>
        <div class="inline-flex mt-12">
          <button class="btn-ui mr-2 px-12">
            Add to bag
          </button>
          <div class="flex items-center rounded-md px-4 py-2 shadow">
            <button>–</button>
            <span class="w-8 text-center">1</span>
            <button>+</button>
          </div>
        </div>
        <div class="mt-12">
          <div class="border-t last:border-b border-ui-medium py-6">
            <h3 class="-my-3 flow-root">
              <button
                class="py-3 bg-white w-full flex items-center justify-between text-sm text-gray-400 hover:text-gray-500"
                type="button"
                @click="showDetails = !showDetails"
              >
                <span class="font-medium text-gray-900">Details</span>
                <span class="ml-6 flex items-center">
                  <span>—</span>
                </span>
              </button>
            </h3>
            <div v-if="showDetails" class="pt-6">
              <div class="space-y-4 text-ui-dark text-sm">
                <ul class="list-inside list-disc space-y-2">
                  <li>Weight: {{ product.weight ? `${product.weight} g` : 'Unknown' }}</li>
                  <li>Width: {{ product.width ? `${product.width} cm` : 'Unknown' }}</li>
                  <li>Height: {{ product.height ? `${product.height} cm` : 'Unknown' }}</li>
                </ul>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'ProductDetail',
  data () {
    return {
      showDetails: false,
      imageToShow: 'default_image',
      product: {
        id: 1,
        title: 'Medusa Coffee Mug',
        description: 'Every programmer's best friend.',
        thumbnail: '',
        variants: [{ prices: [{ amount: 0, currency_code: 'usd' }] }],
        images: [
          { id: 'default_image', url: 'https://picsum.photos/600/400' },
          { id: 'another_image', url: 'https://picsum.photos/600/400?id=100' }
        ]
      }
    }
  },
  computed: {
    lowestPrice () {
      const lowestPrice = this.product.variants.reduce((acc, curr) => {
        return curr.prices.reduce((lowest, current) => {
          if (lowest.amount > current.amount) {
            return current
          }
          return lowest
        })
      }, { amount: 0 })

      return lowestPrice || { amount: 10, currency_code: 'usd' }
    },
    options () {
      if (this.product.options) {
        return this.product.options.map((option) => {
          option.values = option.values.reduce((acc, curr) => {
            if (!acc.find(val => val.value === curr.value)) {
              return [...acc, { ...curr }]
            }
            return acc
          }, [])

          return option
        })
      }
    }
  }
}
</script>
Войдите в полноэкранный режим Выйти из полноэкранного режима

На этой странице отображается вся информация, относящаяся к конкретному товару. Например, размеры, изображения, цена, описание, варианты и т.д….

⚠️ Три страницы включают объект product в функции data для демонстрации дизайна, пока нет сервера для отправки запросов. Как только вы соедините витрину с сервером Medusa, данные, поступающие с сервера, заменят данные в объекте product.

Макеты

Макеты — это отличная помощь, когда вы хотите иметь базовую структуру для вашего приложения Nuxt. Например, чтобы включить навигационную панель и нижний колонтитул, которые будут отображаться на всех страницах приложения. По умолчанию проект Nuxt не содержит макетов, но их легко добавить в ваш проект.

Чтобы иметь макет по умолчанию на витрине магазина, создайте каталог layouts в корне проекта, а внутри него добавьте новый файл default.vue со следующим кодом:

<template>
  <div class="min-h-screen flex flex-col">
    <app-navbar />

    <main class="flex-1">
      <Nuxt />
    </main>

    <app-footer />
  </div>
</template>

<script>
export default {
  name: 'DefaultLayout'
}
</script>
Вход в полноэкранный режим Выйти из полноэкранного режима

Поскольку файл макета был назван default.vue, макет будет автоматически применен ко всем страницам витрины.

Стилизация

Замените содержимое windi.config.ts в корне вашего проекта Nuxt.js на следующее:

import { defineConfig } from '@windicss/plugin-utils'

export default defineConfig({
  /**
   * Write windi classes in html attributes.
   * @see https://windicss.org/features/attributify.html
   */
  attributify: true,
  theme: {
    extend: {
      fontSize: {
        '2xs': '0.5rem'
      },
      maxWidth: {
        '1/4': '25%',
        '1/2': '50%',
        '3/4': '75%'
      },
      maxHeight: {
        review: 'calc(100vh - 10rem)'
      },
      boxShadow: {
        DEFAULT:
          '0 2px 5px 0 rgba(60, 66, 87, 0.08), 0 0 0 1px rgba(60, 66, 87, 0.16), 0 1px 1px rgba(0, 0, 0, 0.12)',
        error:
          '0 2px 5px 0 rgba(255, 155, 155, 0.08), 0 0 0 1px rgba(255, 155, 155, 0.70), 0 1px 1px rgba(0, 0, 0, 0.12)'
      },
      colors: {
        green: {
          DEFAULT: '#56FBB1'
        },
        blue: {
          DEFAULT: '#0A3149'
        },
        ui: {
          light: '#F7F7FA',
          DEFAULT: '#EEF0F5',
          medium: '#D9DFE8',
          dark: '#89959C'
        }
      }
    }
  }
})
Войти в полноэкранный режим Выйти из полноэкранного режима

Изменение порта по умолчанию

Теперь вам нужно изменить порт, на котором по умолчанию запускается приложение магазина (порт 3000). Для этого откройте файл nuxt.config.js и добавьте следующее сразу после свойства ssr

server: {
  port: 3333
},
Войти в полноэкранный режим Выйти из полноэкранного режима

После этого выполните следующую команду, чтобы увидеть в браузере, чего вы добились с помощью компонентов, страниц и макета, которые вы только что установили до этой части руководства.

yarn dev
Войти в полноэкранный режим Выйти из полноэкранного режима

Откройте браузер и перейдите по URL localhost:3000. Вы должны увидеть что-то вроде этого:

Витрина магазина пока просто показывает статические данные. В следующем разделе вы свяжете витрину с сервером Medusa.

Связь сервера Medusa с витриной

Чтобы связать сервер с витриной, сначала откройте проект Medusa в IDE, затем откройте файл .env, в котором заданы все переменные окружения.

Добавьте переменную STORE_CORS со значением URL, на котором будет запущена ваша витрина. Помните, что вы изменили порт по умолчанию на витрине, поэтому URL будет http://localhost:3333.

STORE_CORS=http://localhost:3333
Вход в полноэкранный режим Выйдите из полноэкранного режима

После этого ваш сервер Medusa будет готов принимать запросы от витрины и отправлять ответы, если все работает так, как ожидается.

⚠️ Сервер Medusa по умолчанию работает на порту 9000, поэтому URL, который нужно использовать на витрине для отправки запросов, будет http://localhost:9000.

Тестирование соединения с сервером Medusa

Чтобы иметь возможность выводить список товаров на главной странице, необходимо проверить, можете ли вы отправлять запросы с витрины магазина на сервер Medusa и получать некоторые данные для отображения на главной странице.

⚠️ Убедитесь, что модуль @nuxtjs/axios установлен в вашем проекте витрины. Если это не так, то вам нужно установить его, следуя этим инструкциям.

После того как в проекте установлен модуль axios, необходимо изменить базовый URL для модуля axios, который вы будете использовать для выполнения запросов к серверу.

Откройте файл nuxt.config.js и найдите свойство axios. Измените свойство baseURL так, чтобы оно соответствовало URL, на котором будет запущен сервер medusa:

axios: {
  baseURL: 'http://localhost:9000/'
},
Войти в полноэкранный режим Выйти из полноэкранного режима

Благодаря этому изменению вам не придется вводить полный URL каждый раз, когда вам нужно сделать HTTP-запрос к серверу Medusa. Таким образом, вместо этого:

$axios.$get('http://localhost:9000/store/products')
Войти в полноэкранный режим Выход из полноэкранного режима

вы будете делать следующее:

$axios.$get('/store/products')
Войти в полноэкранный режим Выйти из полноэкранного режима

Если в будущем URL сервера изменится, вам нужно будет только вернуться и обновить его всего один раз, и все будет работать нормально.

Для получения данных из API в этом руководстве используется функция fetch, которую Nuxt.js предлагает как часть ядра.

Откройте файл /pages/index.vue и добавьте функцию fetch в раздел script:

async fetch () {
    try {
      const { products } = await this.$axios.$get('/store/products')
            console.log(products)
      this.products = products
    } catch (e) {
      // eslint-disable-next-line no-console
      console.log('The server is not responding')
    }
  }
Вход в полноэкранный режим Выход из полноэкранного режима

Эта функция получает всего один параметр $axios, который представляет собой сервис, позволяющий сделать HTTP-запрос к серверу Medusa. Таким образом, внутри функции отправляется запрос на конечную точку /store/products для получения списка товаров с сервера Medusa. Затем список продуктов возвращается.

Чтобы проверить это, выполните следующую команду в терминале для запуска сервера Medusa:

medusa develop
Войти в полноэкранный режим Выйти из полноэкранного режима

И запустите сервер витрины:

yarn dev
Войти в полноэкранный режим Выйти из полноэкранного режима

Откройте браузер и перейдите по URL localhost:3000. Затем откройте Web Developer Tools..

Если во вкладке консоли вы найдете что-то подобное, значит, ваше соединение с сервером Medusa работает. В противном случае проверьте, что вы выполнили все шаги и ничего не упустили.

🧠 Помните, что URL, на котором запущено ваше приложение магазина, должен быть тем же самым, который был установлен в переменной STORE_CORS на сервере Medusa.

Отображение товаров на главной странице

Теперь пришло время отобразить результат products, возвращенный с сервера Medusa, на главной странице магазина.

В том же файле /pages/index.vue обновите функцию fetch следующим образом,

async fetch () {
    try {
        const { products } = await this.$axios.$get('/store/products')
        this.products = products.splice(0, 4)
    } catch(e) {
        // eslint-disable-next-line no-console
        console.log('The server is not responding')
  }
}
Вход в полноэкранный режим Выход из полноэкранного режима

При таком обновлении данные, возвращаемые с сервера, заменяют массив products только четырьмя товарами для отображения на главной странице.

Функция v-for, примененная к ProductCard, итерирует массив products и передает компоненту в качестве prop продукт со всеми свойствами, указанными в Medusa API для этой конечной точки.

Если вы посмотрите витрину магазина в браузере, она должна выглядеть примерно так:

Отображение продуктов на странице продуктов

На панели навигации есть ссылка «Продукты». Если вы нажмете на нее, вы будете перенаправлены на страницу продуктов, но там будет только один статичный продукт. Давайте исправим это, чтобы отобразить на странице все продукты вашего сервера Medusa.

Откройте файл /pages/products/index.vue, перейдите в раздел script и добавьте следующую функцию fetch.

async fetch () {
  try {
    const { products } = await this.$axios.$get('/store/products')
    this.products = products
  } catch (e) {
    // eslint-disable-next-line no-console
    console.log('The server is not responding')
  }
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Откройте страницу товаров в браузере и вы должны получить что-то вроде этого:

Display Product Details

Последней страницей, которую необходимо обновить, является страница с подробной информацией о продукте. Если вы нажмете на любой продукт на главной странице или на странице продуктов, вы перейдете на страницу подробностей продукта, но не увидите никаких подробностей. Чтобы исправить это, вам нужно запросить конкретный продукт на сервере Medusa, чтобы вы могли получить всю информацию о продукте.

Откройте файл /pages/products/_id.vue и добавьте следующую функцию fetch.

aasync fetch () {
  try {
    const { product } = await this.$axios.$get(`/store/products/${this.$route.params.id}`)
    this.product = product
    this.imageToShow = this.product.images[0].id
  } catch (e) {
    // eslint-disable-next-line no-console
    console.log('The server is not responding')
  }
},
Вход в полноэкранный режим Выйти из полноэкранного режима

Если вы снова перейдете в браузер и щелкните на любом товаре, вы попадете на страницу с подробной информацией о товаре, как и раньше, но на этот раз вы увидите все детали, отображаемые на странице.

Заключение

Как вы узнали из этого руководства, очень легко создать витрину магазина с нуля с помощью Nuxt.js и интегрировать ее с вашим сервером Medusa.

Следующим шагом для вас будет изучение API Medusa, чтобы узнать обо всех различных запросах, которые вы можете вызвать с вашей витрины, чтобы превратить вашу витрину Nuxt.js в полноценный интернет-магазин.

Например, вы можете реализовать функциональность корзины. Для этого необходимо создать страницы или компоненты в приложении Nuxt.js, а затем сделать соответствующие запросы к серверу Medusa, чтобы получить данные для отображения на витрине.

Если у вас есть какие-либо проблемы или вопросы, связанные с Medusa, обращайтесь к команде Medusa через Discord.

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *