介绍
在线购物,称为电子商务或电子商务,涉及购买和销售商品和服务。在线交易的易用性和安全性使他们在个人和企业中越来越受欢迎。但是,设置电子商务网站并不是一个简单的任务。此过程需要提供出色的客户服务,有效处理订单并存储客户数据。
在本教程中,您将学习如何使用Medusa和Svelte构建表演者电子商务网站。
Svelte电子商务教程源代码可在GitHub上获得。
这是该应用程序的简要预览。
什么是苗条?
Svelte是一种工具,可帮助您创建快速的Web应用程序。它与其他JavaScript框架(例如React和Vue)类似,这使得易于构建交互式用户界面。
但是,Svelte在开发期间将应用程序转换为理想的JavaScript具有优势,而不是在运行时解释代码。
当您的应用程序首次加载时,您不会经历实施成本或延迟的延迟。您可以使用Svelte构建整个应用程序或将其添加到现有代码库中。您还可以创建独立的组件,可以在任何地方工作,而无需使用传统框架的额外负担。
什么是美杜莎?
Medusa是一个开源node.js的composable commerce引擎,为电子商务业务提供了灵活而模块化的解决方案。它的架构由三个基本组件组成:Medusa服务器,管理仪表板和店面。
它包含许多强大的功能,例如货币支持,产品配置,多货币支持以及处理手动订单的能力。
美杜莎还提供基本的电子商务组件,例如Headless Server,Admin和店面作为电子商务堆栈的构建块。借助美杜莎(Medusa)店面,您可以为Android,iOS和Web的任何平台建造电子商务商店。
先决条件
要跟随,请确保您有以下内容:
- JavaScript的基本知识
- Node.js(V16+)安装
- Npm(V8+)安装
设置
美杜莎服务器安装
要在计算机上安装美杜莎,请执行以下步骤:
通过在终端中运行以下命令来安装Medusa CLI:
npm install @medusajs/medusa-cli -g
现在通过运行以下命令来创建新的Medusa服务器:
medusa new my-medusa-store --seed
使用--seed
标志,数据库填充了演示数据,该数据是电子商务商店的起点。
通过在电子商务服务器服务器目录中运行以下命令来启动MEDUSA服务器:
cd my-medusa-store
medusa develop
您现在可以使用邮递员或浏览器等工具进行测试。
打开浏览器,然后转到URL localhost:9000/store/products
如果您在浏览器中找到了类似的内容,则您与Medusa服务器的连接正在正常工作。否则,请查看所有步骤并确保任何内容都没有丢失。
美杜莎管理员安装
在本节中,您将安装Medusa管理员。 Medusa管理员提供了许多电子商务功能,包括管理互动仪表板的Reporter Merchandise授权(RMA)流,商店设置,订单管理和产品管理。您可以在此User Guide中了解更多有关美杜莎管理员及其功能的信息。
这是您需要遵循的步骤来设置Medusa Admin仪表板。
- 通过运行:
git clone https://github.com/medusajs/admin medusa-admin
- 通过运行:更改为新创建的目录:
cd medusa-admin
- 通过运行安装必要的依赖性:
npm install
- 通过导航到持有Medusa管理员并运行的目录来测试它:
npm run start
管理员默认运行在port 7000
上。您可以在localhost:7000
上访问管理页面。您应该看到这样的登录页面:
设置美杜莎时使用â--seed
选项将为您创建一个假管理帐户。电子邮件为admin@medusa-test.com
,密码为supersecret
。使用Medusa Admin帐户,您可以管理商店的产品和收藏,查看订单,管理产品并配置您的商店和地区。
您可以通过Medusa管理员编辑或创建产品。
创建并设置一个苗条的项目
下一步是为电子商务项目创建并设置一个新的Svelte项目。由于它是推荐的设置方法,因此这种苗条的商业将使用SvelteKit。
要创建一个新的Sveltekit项目,请在下面运行命令:
npm create svelte@latest svelte-eCommerce
上面的命令创建了一个新的Svelte项目。
在安装过程中,将有一些可选功能的提示,例如for Code Linting的ESLint
和用于代码格式的Prettier
。确保选择与图像中所示的相同选项。
下一步是运行以下命令:
cd svelte-eCommerce
npm install
npm run dev
此代码设置并运行Svelte项目的开发服务器。这是每行的作用:
-
cd svelte-eCommerce
将当前目录更改为项目目录。 - 您通过运行
npm install
安装依赖项。 - 通过
npm run dev
运行开发服务器启动项目的开发环境。这将编译项目并启动本地服务器,使您可以在浏览器中查看和测试项目。
尾风CSS的设置
您可以使用Tailwind CSS为这个Svelte电子商务网站设计样式。在您的svelte-eCommerce
目录中关注此guide on Setting up Tailwind CSS in a SvelteKit project。
安装依赖项
导航到您的svelte-eCommerce
目录并安装以下依赖项
npm i axios svelte-icons-pack sweetalert2
此命令安装三个NPM软件包:Axios,Svelte-Icons-Pack和SweetAlert2。
-
axios
是一个流行的JavaScript库,提供了一个简单的API,用于向服务器提出HTTP请求。它在浏览器和node.js环境中工作,通常用于从Web API发送和接收数据。 -
svelte-icons-pack
是一个与Svelte框架一起使用的图标。该软件包提供了可以轻松在Svelte应用程序中使用的图标的集合。 -
sweetalert2
是一个JavaScript库,用于创建美丽,响应和可自定义的警报对话框。它通常用于向用户提供反馈或提示他们在Web应用程序中输入。
Medusa与Svelte Storefront
您在这里开始构建Svelte电子商务应用程序。
更改Svelte端口
在svelte-eCommerce/vite.config.js
文件中,将现有代码替换为以下代码
import { sveltekit } from '@sveltejs/kit/vite';
/** @type {import('vite').UserConfig} */
const config = {
plugins: [sveltekit()],
server: {
port: 8000,
strictPort: false,
},
};
export default config;
配置对象指定以下选项:
-
server
选项指定开发服务器应在(8000)上侦听的端口,以及服务器是否只能在该确切端口上收听或允许任何可用端口(strictPort: false
)。
默认情况下,Medusa服务器允许在端口
8000
上的店面连接。
创建基本URL环境变量
创建一个环境文件来存储您的基本URL可以使开发环境和生产环境之间的切换变得轻而易举。它保留敏感信息,例如API键,与您的代码库分开。
在svelte-eCommerce
目录中创建一个带有以下内容的.env
文件:
VITE_API_BASE_URL="http://localhost:9000"
BASE_URL
环境变量是您的Medusa服务器的URL。
创建实用程序功能
在src
目录中,创建一个名称util
的文件夹。该文件夹将包含产品的sharedload
功能。位于此目录中的sharedload
函数负责从API
检索数据,并在必要时设计在整个项目中可重复使用。
在src/util/shared.js
文件中,粘贴以下代码:
// @ts-nocheck
export const getProducts = async () => {
try {
const productres = await fetch(`${import.meta.env.VITE_API_BASE_URL}/store/products/`);
const productdata = await productres.json();
const products = productdata?.products;
return {
products: products,
};
} catch (error) {
console.log("Error: " + error);
}
};
前面的代码导出了从API获取数据的getProducts
函数。使用Fetch API请求产品列表。这导致API响应提供产品列表。最后,该函数返回带有产品列表作为属性的对象。注意如何使用.env
文件中定义的BASE_URL
。
存储组件
现在是时候创建一个可重复使用的组件来显示产品了。
Header
,Footer,
和Products
都属于本节。这些组件中的每一个都会出现在您的店面的不同页面上。
组件目录
在src
目录中,创建一个名称components
的文件夹。目录将包含产品的所有可重复使用组件。
Navbar Compont
在src
目录中,创建文件src/components/Navbar.svelte
并添加以下代码:
<script>
export let productId;
import Icon from "svelte-icons-pack/Icon.svelte";
import AiOutlineShoppingCart from "svelte-icons-pack/ai/AiOutlineShoppingCart";
</script>
<div>
<div class="fixed z-50 bg-white topNav w-full top-0 p-3 md:bg-opacity-0">
<div class="max-w-6xl relative flex mx-auto flex-col md:flex-row">
<a href="#/" class="md:hidden absolute top-1 right-14">
<div class="relative">
<div class="relative">
<Icon src="{AiOutlineShoppingCart}" />
{#if productId?.length >= 1}
<div
class="absolute px-1 bg-red-400 -top-1 -right-1 rounded-full border-2 border-white text-white"
id="cart"
style="font-size: 10px"
>
{productId?.length}
</div>
{/if}
</div>
</div>
</a>
<div class="flex-grow font-bold text-lg">
<a href="/">
<span>Best Store</span>
</a>
</div>
<div class="menu hidden md:flex flex-col md:flex-row mt-5 md:mt-0 gap-16">
<div class="flex flex-col md:flex-row gap-12 capitalize">
<div class="text-red-400 font-bold border-b border-red-400">
<a href="/"> home</a>
</div>
<div class="text-red-400 font-bold border-b border-red-400">
<a href="/products">products</a>
</div>
</div>
<div class="flex gap-12">
<a href="#/" class="hidden md:block">
<div class="relative">
<Icon src="{AiOutlineShoppingCart}" />
{#if productId?.length >= 1}
<div
class="absolute px-1 bg-red-400 -top-1 -right-1 rounded-full border-2 border-white text-white"
id="cart"
style="font-size: 10px"
>
{productId?.length}
</div>
{/if}
</div>
</a>
</div>
</div>
</div>
</div>
</div>
上面的代码是呈现页面顶部的导航栏的头部部分组件。主页,产品和购物车图标都显示在导航栏中。如果购物车已订购物品,则在图标上显示徽章,显示购物车中的物品数量。
导航栏隐藏在小屏幕上,并在较大屏幕上显示为下拉菜单。该组件还包括用于处理钥匙按下的逻辑和链接和购物车图标的单击。
页脚组件
在src
目录中,创建文件src/components/Footer.svelte
并添加以下代码:
<div>
<div>
<div class="bg-red-400 py-32 PX-4">
<div class="max-w-6xl gap-6 mx-auto grid grid-cols-1 md:grid-cols-9">
<div class="md:col-span-3 py-3 space-y-4">
<div class="text-2xl font-bold text-gray-100">Best Store</div>
<div class="text-gray-300 w-60 pr-0">
At best store, we offer top-quality Hoddies, Joggers, Shorts and a
variety of other items. We only sell the best grade of products.
</div>
</div>
<div class="md:col-span-2 py-3 space-y-4">
<div class="text-2xl font-bold text-gray-100">Information</div>
<div class="text-gray-300 w-60 space-y-2 pr-0">
<div class="">About Us</div>
<div class="">Career</div>
</div>
</div>
<div class="md:col-span-2 py-3 space-y-4">
<div class="text-2xl font-bold text-gray-100">Our Services</div>
<div class="text-gray-300 w-60 space-y-2 pr-0">
<div class="">Clothing</div>
<div class="">Fashion</div>
<div class="">Branding</div>
<div class="">Consultation</div>
</div>
</div>
<div class="md:col-span-2 py-3 space-y-4">
<div class="text-2xl font-bold text-gray-100">Contact Us</div>
<div class="text-gray-300 w-60 space-y-2 pr-0">
<div class="">+234 070-000-000</div>
<div class="">care@best.com</div>
<div class="">Terms & Privacy</div>
</div>
</div>
</div>
</div>
</div>
</div>
上面的代码是在页面底部呈现页脚的底部组件。页脚包含有关Svelte商店,其服务及其联系方式的信息。页脚将在以后使用。
存储页面
现在,您必须为Svelte电子商务店面创建页面。
在Svelte创建一个新项目后,它带有一些默认文件,包括+page.svelt和+page.js
+page.svelte
a +page.svelte
组件定义了您的应用程序页面。默认情况下,在服务器(SSR)的初始请求和浏览器(CSR)中呈现页面上的页面。
+page.js
通常,一个页面必须加载一些数据才能渲染。为此,我们添加一个导出负载函数的模块:
在src/routes
目录中,将文件中的内容替换为src/routes/+page.svelte
中的内容。
主页
在src/routes/+page.svelte
文件中,用以下内容替换其内容:
<script>
// @ts-nocheck
import Footer from "../components/Footer.svelte";
import "../app.css";
import Navbar from "../components/Navbar.svelte";
import { writable, derived } from "svelte/store";
// export let data;
import {getProducts} from '../util/shared';
let products;
const productData = async () => {
const data = await getProducts();
products = data;
console.log(products, 'products')
}
$: productData()
</script>
<div>
<Navbar />
<div class="mt-40">
<div class="flex">
<div class="flex-grow text-4xl font-extrabold text-center">
Best Qualities You Can Trust
</div>
</div>
<div class="flex">
<div class="flex-grow text-4xl mt-12 font-extrabold text-center">
<a
href="/products"
class="bg-red-400 hover:bg-red-700 text-white font-bold py-2 px-4 rounded "
>Products</a
>
</div>
</div>
<div
class="max-w-12xl mx-auto h-full flex flex-wrap justify-center py-28 gap-10"
>
{#if products}
{#each products?.products as product, i}
<div class="">
<div
class="rounded-lg shadow-lg bg-white max-w-sm"
>
<a
href={`/products/${product.id}`}
data-mdb-ripple="true"
data-sveltekit-prefetch
data-mdb-ripple-color="light"
>
<img class="rounded-t-lg" src={product.thumbnail} alt="" />
</a>
<div
class="bg-red-400 py-8 relative font-bold text-gray-100 text-xl w-full flex flex-col justify-center px-6"
>
<div class="">{product.title}</div>
<div class="">
€ {product.variants[0].prices[0].amount / 100}
</div>
</div>
</div>
</div>
{/each}
{/if}
</div>
</div>
<Footer />
</div>
上面的代码是Svelte电子商务商店的主页。显示产品列表。页面顶部出现了一个导航栏,然后是产品详细信息部分和底部的页脚。
产品页面
首先,在"src/routes"
目录中创建一个“产品”文件夹,然后在文件夹中创建两个新文件,"+page.svelte"
和"+page.js"
。
将以下代码添加到src/routes/products/+page. Svelte
文件:
<script>
// @ts-nocheck
import Navbar from "../../components/Navbar.svelte";
// @ts-nocheck
import "../../app.css";
import { getProducts } from "../../util/shared";
let products;
const productData = async () => {
const data = await getProducts();
products = data;
console.log(products, 'products')
}
$: productData()
</script>
<Navbar />
<div class="mt-40">
<div class="flex">
<div class="flex-grow text-4xl font-extrabold text-center">
Best Qualities You Can Trust
</div>
</div>
<div
class="max-w-12xl mx-auto h-full flex flex-wrap justify-center py-28 gap-10"
>
{#each products?.products as product, i}
<div class="">
<div
class="rounded-lg shadow-lg bg-white max-w-sm"
>
<a
href={`/products/${product?.id}`}
data-mdb-ripple="true"
data-sveltekit-prefetch
data-mdb-ripple-color="light"
>
<img class="rounded-t-lg" src={product?.thumbnail} alt="" />
</a>
<div
class="bg-red-400 py-8 relative font-bold text-gray-100 text-xl w-full flex flex-col justify-center px-6"
>
<div class="">{product?.title}</div>
<div class="">
€ {product?.variants[0]?.prices[0]?.amount / 100}
</div>
</div>
</div>
</div>
{/each}
</div>
</div>
以前的代码显示了产品卡的网格。它由顶部的导航栏和下面的产品卡片组成。每张产品卡都包含图像,标题和价格。
该组件还包括用于处理产品卡上点击的逻辑,并将用户带到单个产品页面。
组件使用thegetProducts
函数获取产品列表,然后使用 each
块在网格中显示。每个块均在产品列表上迭代,并为每个产品创建产品卡。
单产品页面
在src/routes/products
目录中创建一个新的[id]
文件夹,其中两个文件src/routes/products/[id]/+page.js
和src/routes/products/[id]/+svelte.svelte
src/routes/products/[id]
文件夹使用a parameter (id
)创建一条路由,当用户请求像[/products/prod_01GN6666V4R3KWPPTJ0GMD6T](http://localhost:8000/singlepage/prod_01GN6666V4R3KWPPTJ0GMD6TD4)
在src/routes/products/[id]/+page.js
目录中,添加以下代码:
/* eslint-disable no-unused-vars */
// @ts-ignore
export const load = async ({ fetch, params }) => {
return {
params
};
}
此代码定义一个称为
load
的异步函数,该函数导出包含params
对象的单个对象。 加载函数接收一个具有两个属性的对象:获取和参数。
在src/routes/products/[id]/+page.svelte
文件中,添加以下代码:
<script>
// @ts-nocheck
import "../../../app.css";
import "../../../components/Navbar.svelte";
import axios from "axios";
import Navbar from "../../../components/Navbar.svelte";
import Swal from 'sweetalert2'
export let data;
let responseData = [];
let currentImg = 0;
let currentSize = "S";
let currentPrice = "";
let variantsId = 0;
let cartId = "";
let variantTitle;
let products = [];
import { writable, derived } from "svelte/store";
import {browser} from '$app/environment'
export const cartDetails = writable({
cart_id: '',
})
if (!cartId) {
axios({
method: 'post',
url: `${import.meta.env.VITE_API_BASE_URL}/store/carts`,
withCredentials: true
})
.then(response => {
console.log(response.data.cart.id, 'response.data.cart.id')
localStorage.setItem("cart_id", response.data.cart.id)
})
.catch(error => {
console.log(error);
});
}
const fetchData = async () => {
cartId = browser && localStorage.getItem('cart_id')
axios
.get(`${import.meta.env.VITE_API_BASE_URL}/store/products/${data.params.id}`).then((response) => {
if (response.data.product) {
responseData = response?.data
}
})
.catch((err) => {
console.log("error", err)
});
};
$: fetchData();
</script>
<div class="mt-40">
<main>
<Navbar productId={JSON.parse( browser && localStorage.getItem('cart'))} />
<div class="py-20 px-4">
<div class="text-white max-w-6xl mx-auto py-2">
<div class="grid md:grid-cols-2 gap-20 grid-cols-1">
<div>
<div class="relative">
<div>
<div class="relative">
<img src={responseData.product?.images[currentImg]?.url} alt="no image_" />
<div class="absolute overflow-x-scroll w-full bottom-0 right-0 p-4 flex flex-nowrap gap-4">
<div class="flex w-full flex-nowrap gap-4 rounded-lg">
{#if responseData?.product?.images}
{#each responseData.product.images as img, i}
<div
key={i}
on:click={() => (currentImg = i)}
title={responseData.product.images[i].url}
class="w-16 h-24 flex-none"
on:keydown={() => (currentImg = i)}
>
<div
class="h-full w-full rounded-lg cursor-pointer shadow-lg border overflow-hidden"
>
<img
src={responseData.product.images[i].url}
alt=""
class="h-full w-full"
/>
</div>
</div>
{/each}
{/if}
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<div class="flex md:flex-col flex-col space-y-7 justify-center">
<div class="text-black space-y-3">
<h2 class="font-bold text-xl text-black">{responseData?.product?.title}</h2>
<p class="text-sm">{responseData?.product?.description}</p>
</div>
<div class="space-y-3">
<div class="font-bold text-md text-black">Select Size</div>
<div class="flex flex-row flex-wrap gap-4">
{#if responseData?.product?.variants}
{#each responseData?.product?.variants as variant, i}
{ variantTitle = variant.title.split("/")[0]}
<div>
<div
on:click={() => {
currentSize = variant?.title?.split("/")[0]
currentPrice = variant?.prices[0]?.amount[i]
variantsId = variant.id
}}
on:keydown={() => {
currentSize = variant?.title?.split("/")[0]
currentPrice = variant?.prices[0]?.amount[i]
variantsId = variant.id
}}
class={currentSize === variant?.title?.split("/")[0] ? 'border-purple-300 bg-red-400' : 'border-gray-100'}
contenteditable={false}
>
<span class="text-black text-sm">{variant?.title?.split("/")[0]}</span>
</div>
</div>
{/each}
{/if}
</div>
</div>
<div class="space-y-3">
<div class="font-bold text-md text-black">Price</div>
{#if responseData?.product?.variants}
<div class="text-black">${responseData?.product?.variants.map(x => x.prices[0]?.amount)[0]}</div>
{/if}
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
上面的代码获取产品信息并将其显示在页面上。
添加到购物车实施
本节的目的是解释添加到车的功能如何工作。
在src/routes/products/[id]/+page.svelte
文件中,在fetchData
功能下方添加addProduct
函数,并在价格标签下方添加产品的按钮。
<script>
// ...
const addProduct = async (data) => {
let cartId = browser && localStorage.cart_id;
try {
const response = await axios.post(
`${import.meta.env.VITE_API_BASE_URL}/store/carts/${localStorage.cart_id}/line-items`,
{
variant_id: data,
quantity: 1,
metadata: {
size: currentSize,
},
}
);
products = [response.data?.cart];
browser && localStorage.setItem("cart", JSON.stringify([products]));
if (response?.data?.cart) {
if (response?.status === 200) {
Swal.fire({
icon: "success",
title: "Item Added to Cart Successfully",
showConfirmButton: true,
}).then((res) => {
if (res.isConfirmed) {
window.location.reload();
}
});
}
}
} catch (error) {
console.log(error);
}
};
// ...
</script>
<div class="mt-40">
<main>
<!-- ... -->
<button
class="bg-red-400 text-white font-bold py-2 px-4 rounded-full"
on:click={() => {
if (variantsId === 0) {
alert("Please select a size before adding to cart.");
} else {
addProduct(variantsId);
}
}}
>
Add to Cart
</button>
<!-- ... -->
</main>
</div>
使用选定的variant ID
使用addProduct
功能将产品添加到购物车中。然后,代码使用Svelte绑定和事件听众显示产品数据。
测试Svelte电子商务
按照以下步骤测试您的苗条电子商务:
导航到您的Medusa服务器并运行:
medusa develop
导航到您的svelte-eCommerce
目录并运行:
npm run dev
您的Svelte电子商务店面现在正在localhost:8000
要查看主页,请在浏览器中访问localhost:8000。您应该看到主页。
您可以通过单击它们来查看任何产品的详细信息。
单击添加到购物车按钮。
结论
本教程演示了如何将Svelte电子商务应用程序连接到MEDUSA服务器并实现“添加到购物车”功能,该功能使客户可以将项目添加到购物车并管理其内容。可以在Medusa服务器的帮助下添加到应用程序中的其他功能包括checkout flow,用于与Stripe这样的付款提供商放置订单和集成。
在使用美杜莎(Medusa)经营电子商务商店时,可能性是无穷无尽的,包括但不限于;
- 整合了诸如PayPal之类的付款提供商
- 使用Meilisearch将产品搜索引擎添加到您的店面
- 使用允许客户授权并管理其会议的Authenticate Customer endpoint对客户进行身份验证。
- 此外,请查看如何在此tutorial中使用身份验证的客户端点。
如果您有与美杜莎有关的任何问题或问题,然后随时通过Discord与Medusa团队联系。