V tomto článku se naučíme, jak vytvořit skutečný systém prodeje vstupenek pomocí Strapi a Vue.js, kde si uživatelé mohou zakoupit vstupenky na nadcházející události. Naší případovou studií bude systém nákupu vstupenek na připravované filmy.
Co budete pro tento tutoriál potřebovat
- Základní znalost Vue.js
- Znalost JavaScriptu
- Node.js (verze 14 doporučená pro Strapi)
Obsah
- Krátký úvod do Strapi, bezhlavého CMS
- Lešení projektu Strapi
- Vytváření sbírek vstupenek
- Vytváření sbírek událostí
- Nasazení databáze
- Povolení přístupu veřejnosti
- Vytvoření aplikace Vue.js
- Nastavení CSS Tailwind
- Stavební komponenty a pohledy
- Ruční úprava backendu Strapi
- Závěr
Dokončená verze vaší aplikace by měla vypadat jako na obrázku níže:
Krátký úvod do Strapi, bezhlavého CMS
Dokumentace Strapi říká, že Strapi je flexibilní, open-source, bezhlavý CMS, který dává vývojářům svobodu ve výběru jejich oblíbených nástrojů a frameworků a umožňuje editorům snadno spravovat a distribuovat jejich obsah.
Strapi nám pomáhá rychle vytvořit API bez problémů s vytvářením serveru od začátku. Se Strapi můžeme dělat všechno doslova a je to snadno přizpůsobitelné. Můžeme snadno přidat náš kód a upravit funkce. Strapi je úžasný a jeho schopnosti by vás nechaly omráčit.
Strapi poskytuje administrátorský panel pro úpravy a vytváření API. Poskytuje také snadno upravitelný kód a používá JavaScript.
Lešení projektu Strapi
Chcete-li nainstalovat Strapi, přejděte do dokumentace Strapi na Strapi. Pro tento projekt budeme používat databázi SQLite. Chcete-li nainstalovat Strapi, spusťte následující příkazy:
yarn create strapi-app my-project # using yarn
npx create-strapi-app@latest my-project # using npx
Nahraďte my-project
s názvem, kterému chcete volat adresář aplikací. Váš správce balíčků vytvoří adresář se zadaným názvem a nainstaluje Strapi.
Pokud jste postupovali podle pokynů správně, měli byste mít na svém počítači nainstalován Strapi. Spusťte následující příkazy pro spuštění vývojového serveru Strapi:
yarn develop # using yarn
npm run develop # using npm
Vývojový server spustí aplikaci na http://localhost:1337/admin.
Vytváření sbírek událostí
Vytvořme náš Event
typ sbírky:
- Klikněte na
Content-Type Builder
podPlugins
v postranní nabídce. - Pod
collection types
, klikněte nacreate new collection type
. - Vytvořte nový
collection-type
s názvemEvent
. - V části typ obsahu produktu: vytvořte následující pole
name
jakoshort text
date
jakoDatetime
image
jakomedia
(jedno médium)price
jakoNumber
(desítkovétickets-available
jakoNumber
Konečný Event
typ kolekce by měl vypadat jako na obrázku níže:
Vytváření sbírek vstupenek
Dále vytvoříme Ticket
typ sbírky:
- Klikněte na
Content-Type Builder
podPlugins
v postranní nabídce. - Pod
collection types
, klikněte nacreate new collection type
- Vytvořte nový
collection-type
s názvemTicket
. - Vytvořte následující pole pod typem obsahu produktu:
reference_number
jakoUID
seats_with
jakoNumber
seats_without
jakoNumber
total
jakoNumber
total_seats
jakoNumber
event
jakorelation
(Událost má mnoho vstupenek.)
Konečný Ticket
typ kolekce by měl vypadat jako na obrázku níže:
Nasazení databáze
Chcete-li databázi nasadit, vytvořte některá data pod Events
typ sbírky. Chcete-li to provést, postupujte podle následujících kroků:
- Klikněte na
Content Manager
v postranní nabídce. - Pod
collection types
, vyberteEvent
. - Klikněte na
create new entry
. - Vytvořte tolik nových položek, kolik chcete.
Povolení veřejného přístupu
Strapi má uživatelská oprávnění a role, které jsou přiřazeny authenticated
a public
uživatelů. Protože náš systém nevyžaduje přihlášení a registraci uživatele, musíme našemu Content types
povolit veřejný přístup .
Chcete-li povolit veřejný přístup, postupujte takto:
- Klikněte na
Settings
podgeneral
v postranní nabídce. - Pod
User and permission plugins
, klikněte naRoles
. - Klikněte na
public
. - Pod
permissions
, jinýcollection types
jsou uvedeny. Klikněte naEvent
, pak zaškrtněte oběfind
afindOne
. - Dále klikněte na
Ticket
. - Zkontrolujte
create
,find
afindOne
. - Nakonec klikněte na
save
.
Úspěšně jsme umožnili veřejný přístup k našim typům obsahu; nyní můžeme vytvořit API
volá správně.
Vytváření aplikace Vue.js
Dále nainstalujeme a nakonfigurujeme Vue.Js, aby fungoval s naším backendem Strapi.
Chcete-li nainstalovat Vue.js pomocí balíčku @vue/CLI, navštivte dokumenty Vue CLI nebo spusťte jeden z těchto příkazů.
npm install -g @vue/cli
# OR
yarn global add @vue/cli
Jakmile nainstalujete Vue CLI na místní počítač, spusťte následující příkazy a vytvořte projekt Vue.js.
vue create my-project
Nahraďte my-project
s názvem, který chcete nazývat svůj projekt.
Výše uvedený příkaz by měl spustit aplikaci příkazového řádku, která vás provede vytvořením projektu Vue.js. Vyberte libovolné možnosti, ale vyberte Router
, Vuex
a linter/formatter
protože první dva jsou v naší aplikaci zásadní. Poslední věcí je pěkně naformátovat kód.
Poté, co Vue CLI dokončí vytváření vašeho projektu, spusťte následující příkaz.
cd my-project
yarn serve //using yarn
npm serve //using npm
Nakonec navštivte následující adresu URL:[http://localhost:8080](http://localhost:8080/)
otevřete aplikaci Vue.js ve vašem prohlížeči.
Nastavení CSS Tailwind
Jako náš CSS framework použijeme Tailwind CSS. Pojďme se podívat, jak můžeme integrovat Tailwind CSS do naší aplikace Vue.js.
npm install -D tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
or
yarn add tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
V kořenovém adresáři vaší složky Vue.js vytvořte postcss.config.js
a napište následující řádky.
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
}
}
V kořenovém adresáři složky Vue.js také vytvořte tailwindcss.config.js
a napište následující řádky.
module.exports = {
purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
}
Komponenty fontu jsme rozšířili přidáním některých fontů, které budeme používat. Tyto fonty musí být nainstalovány na vašem místním počítači, aby správně fungovaly, ale můžete použít jakákoli písma, která se vám líbí.
Nakonec vytvořte index.css
soubor ve vašem src
složku a přidejte následující řádky.
/* ./src/main.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
Instalace Axios pro volání API
Potřebujeme balíček pro volání API do našeho Stripi backend a budeme používat Axios balíček pro tento účel.
Spuštěním následujícího příkazu nainstalujte Axios na vašem stroji.
npm install --save axios
or
yarn add axios
Stavební komponenty
V této části vytvoříme komponenty, které tvoří naši aplikaci vue.js.
Vytvoření komponenty „EventList“:
Vytvořte EventList.vue
soubor umístěný v src/components
složku a do souboru přidejte následující řádky kódu.
<template>
<div class="list">
<div v-for="(event, i) in events" :key="i" class="mb-3">
<figure
class="md:flex bg-gray-100 rounded-xl p-8 md:p-0 dark:bg-gray-800"
>
<img
class="w-24 h-24 md:w-48 md:h-auto md:rounded-none rounded-full mx-auto"
:src="`http://localhost:1337${event.attributes.image.data.attributes.formats.large.url}`"
alt=""
width="384"
height="512"
/>
<div class="pt-6 md:p-8 text-center md:text-left space-y-4">
<blockquote>
<h1 class="text-xl md:text-2xl mb-3 font-bold uppercase">
{{ event.attributes.name }}
</h1>
<p class="text-sm md:text-lg font-medium">
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Debitis
dolore dignissimos exercitationem, optio corrupti nihil veniam
quod unde reprehenderit cum accusantium quaerat nostrum placeat,
sapiente tempore perspiciatis maiores iure esse?
</p>
</blockquote>
<figcaption class="font-medium">
<div class="text-gray-700 dark:text-gray-500">
tickets available: {{ event.attributes.tickets_available == 0 ? 'sold out' : event.attributes.tickets_available }}
</div>
<div class="text-gray-700 dark:text-gray-500">
{{ formatDate(event.attributes.date) }}
</div>
</figcaption>
<!-- <router-link to="/about"> -->
<button :disabled=" event.attributes.tickets_available == 0 " @click="getDetail(event.id)" class="bg-black text-white p-3">
Get tickets
</button>
<!-- </router-link> -->
</div>
</figure>
</div>
</div>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
events: [],
};
},
methods: {
getDetail(id) {
console.log("btn clicked");
this.$router.push(`/event/${id}`);
},
formatDate(date) {
const timeArr = new Date(date).toLocaleTimeString().split(":");
const DorN = timeArr.pop().split(" ")[1];
return `${new Date(date).toDateString()} ${timeArr.join(":")} ${DorN}`;
},
},
async created() {
const res = await axios.get("http://localhost:1337/api/events?populate=*");
this.events = res.data.data;
},
};
</script>
<style scoped></style>
Vytvoření komponenty „EventView“:
Vytvořte EventView.vue
soubor umístěný v src/components
složku a do souboru přidejte následující řádky kódu.
<template>
<div class="">
<!-- showcase -->
<div
:style="{
backgroundImage: `url(${img})`,
backgroundColor: `rgba(0, 0, 0, 0.8)`,
backgroundBlendMode: `multiply`,
backgroundRepeat: `no-repeat`,
backgroundSize: `cover`,
height: `70vh`,
}"
class="w-screen flex items-center relative"
ref="showcase"
>
<div class="w-1/2 p-5">
<h1 class="text-2xl md:text-6xl text-white mb-3 uppercase font-bold my-auto">
{{ event.attributes.name }}
</h1>
<p class="leading-normal md:text-lg mb-3 font-thin text-white">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Velit natus
illum cupiditate qui, asperiores quod sapiente. A exercitationem
quidem cupiditate repudiandae, odio sequi quae nam ipsam obcaecati
itaque, suscipit dolores.
</p>
<p class="text-white"><span class="font-bold">Tickets available:</span> {{ event.attributes.tickets_available }} </p>
<p class="text-white"><span class="font-bold">Airing Date:</span> {{ formatDate(event.attributes.date) }}</p>
</div>
</div>
<div class="text-center flex justify-center items-center">
<div class="mt-3 mb-3">
<h3 class="text-4xl mt-5 mb-5">Get Tickets</h3>
<table class="table-auto w-screen">
<thead>
<tr>
<th class="w-1/2">Options</th>
<th>Price</th>
<th>Quantity</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr class="p-3">
<td class="p-3">Seats without popcorn and drinks</td>
<td class="p-3">${{ formatCurrency(price_of_seats_without) }}</td>
<td class="p-3">
<select class="p-3" id="" v-model="no_of_seats_without">
<option
class="p-3 bg-dark"
v-for="(num, i) of quantityModel"
:key="i"
:value="`${num}`"
>
{{ num }}
</option>
</select>
</td>
<td>${{ formatCurrency(calcWithoutTotal) }}</td>
</tr>
<tr class="p-3">
<td class="p-3">Seats with popcorn and drinks</td>
<td class="p-3">${{ formatCurrency(price_of_seats_with) }}</td>
<td class="p-3">
<select class="p-3" id="" v-model="no_of_seats_with">
<option
class="p-3 bg-black"
v-for="(num, i) of quantityModel"
:key="i"
:value="`${num}`"
>
{{ num }}
</option>
</select>
</td>
<td>${{ formatCurrency(calcWithTotal) }}</td>
</tr>
</tbody>
</table>
<div class="m-3">
<p class="mb-3">Ticket Total: ${{ formatCurrency(calcTotal) }}</p>
<button
@click="bookTicket"
:disabled="calcTotal == 0"
class="bg-black text-white p-3"
>
Book Now
</button>
</div>
</div>
</div>
<ticket
:data="res"
class="mx-auto h-full z-10 absolute top-0"
v-if="booked == true"
/>
</div>
</template>
<script>
import axios from "axios";
import randomstring from "randomstring";
import ticket from "../components/Ticket.vue";
export default {
data() {
return {
quantityModel: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
no_of_seats_without: 0,
price_of_seats_without: 3,
no_of_seats_with: 0,
price_of_seats_with: 4,
id: "",
event: {},
img: "",
booked: false,
};
},
components: {
ticket,
},
methods: {
getDetail() {
console.log("btn clicked");
this.$router.push("/");
},
assignValue(num) {
console.log(num);
this.no_of_seats_without = num;
},
async bookTicket() {
console.log("booking ticket");
console.log(this.booked, "booked");
try {
const res = await axios.post(`http://localhost:1337/api/tickets`, {
data: {
seats_with: this.no_of_seats_with,
seats_without: this.no_of_seats_without,
total_seats:
parseInt(this.no_of_seats_without) +
parseInt(this.no_of_seats_with),
total: this.calcTotal,
event: this.id,
reference_number: randomstring.generate(),
},
});
this.res = res.data;
this.res.event = this.event.attributes.name;
this.res.date = this.event.attributes.date;
this.booked = true;
this.no_of_seats_with = 0;
this.no_of_seats_without = 0;
} catch (error) {
return alert(
"cannot book ticket as available tickets have been exceeded. Pick a number of ticket that is less than or equal to the available tickets"
);
}
},
formatCurrency(num) {
if (num.toString().indexOf(".") != -1) {
return num;
} else {
return `${num}.00`;
}
},
formatDate(date) {
const timeArr = new Date(date).toLocaleTimeString().split(":");
const DorN = timeArr.pop().split(" ")[1];
return `${new Date(date).toDateString()} ${timeArr.join(":")} ${DorN}`;
},
},
computed: {
calcWithoutTotal() {
return (
parseFloat(this.no_of_seats_without) *
parseFloat(this.price_of_seats_without)
);
},
calcWithTotal() {
return (
parseFloat(this.no_of_seats_with) * parseFloat(this.price_of_seats_with)
);
},
calcTotal() {
return this.calcWithoutTotal + this.calcWithTotal;
},
},
async created() {
this.id = this.$route.params.id;
try {
const res = await axios.get(
`http://localhost:1337/api/events/${this.$route.params.id}?populate=*`
);
this.event = res.data.data;
this.price_of_seats_without = res.data.data.attributes.price;
this.price_of_seats_with = res.data.data.attributes.price + 2;
const img =
res.data.data.attributes.image.data.attributes.formats.large.url;
this.img = `"http://localhost:1337${img}"`;
} catch (error) {
return alert('An Error occurred, please try agian')
}
},
};
</script>
<style scoped></style>
Vytváření sbírek vstupenek
Vytvořte Ticket.vue
soubor umístěný v src/components
složku a do souboru přidejte následující řádky kódu.
<template>
<div
class="h-full w-full modal flex overflow-y-hidden justify-center items-center"
>
<div class="bg-white p-5">
<p class="m-2">
Show: <span class="uppercase">{{ data.event }}</span>
</p>
<p class="m-2">Date: {{ formatDate(data.date) }}</p>
<p class="m-2">TicketID: {{ data.reference_number }}</p>
<p class="m-2">
Seats without Pop corn and Drinks: {{ data.seats_without }} seats
</p>
<p class="m-2">
Seats with Pop corn and Drinks: {{ data.seats_with }} seats
</p>
<p class="m-2">
Total seats:
{{ parseInt(data.seats_without) + parseInt(data.seats_with) }} seats
</p>
<p class="m-2">Price total: ${{ data.total }}.00</p>
<router-link to="/">
<button class="m-2 p-3 text-white bg-black">Done</button>
</router-link>
</div>
</div>
</template>
<script>
export default {
name: "Ticket",
data() {
return {};
},
props: ["data"],
components: {},
methods: {
formatDate(date) {
const timeArr = new Date(date).toLocaleTimeString().split(":");
const DorN = timeArr.pop().split(" ")[1];
return `${new Date(date).toDateString()} ${timeArr.join(":")} ${DorN}`;
},
},
};
</script>
<style scoped>
.show_case {
/* background: rgba(0, 0, 0, 0.5); */
/* background-blend-mode: multiply; */
background-repeat: no-repeat;
background-size: cover;
}
.show_img {
object-fit: cover;
opacity: 1;
}
._img_background {
background: rgba(0, 0, 0, 0.5);
}
.modal {
overflow: hidden;
background: rgba(0, 0, 0, 0.5);
}
</style>
Zobrazení budovy
V této části použijeme komponenty vytvořené v poslední části k sestavení stránek na našem frontendu.
Vytvoření zobrazení „Události“
Events
stránka využívá EventsView.vue
komponentu, kterou jsme vytvořili v předchozí části.
Vytvořte Event.vue
soubor umístěný v src/views
složku a upravte obsah souboru na následující:
<template>
<div class="about">
<event-view />
</div>
</template>
<script>
import EventView from "../components/EventView.vue";
export default {
name: "Event",
components: {
EventView,
},
};
</script>
<style scoped>
.show_case {
/* background: rgba(0, 0, 0, 0.5); */
/* background-blend-mode: multiply; */
background-repeat: no-repeat;
background-size: cover;
}
.show_img {
object-fit: cover;
opacity: 1;
}
._img_background {
background: rgba(0, 0, 0, 0.5);
}
</style>
Vytvoření zobrazení „Domů“:
Home
stránka využívá EventList.vue
komponentu, kterou jsme vytvořili v předchozí části.
Vytvořte Home.vue
soubor umístěný v src/views
složku a upravte obsah souboru na následující:
<template>
<div class="home">
<h1 class="text-center text-xl mb-3 font-bold mt-4">Upcoming Events</h1>
<div class="flex self-center justify-center">
<event-list class="w-5/6" />
</div>
</div>
</template>
<script>
// @ is an alias to /src
import EventList from "../components/EventList.vue";
export default {
name: "Home",
components: {
EventList,
},
};
</script>
Aktualizace směrovače Vue
Vytvořili jsme některé nové soubory zobrazení, které musíme zpřístupnit jako trasy. Aby k tomu však došlo, musíme aktualizovat náš router, aby odrážel provedené změny.
Chcete-li provést změny na routeru Vue, postupujte podle následujících kroků:
- Otevřete
index.js
soubor umístěný nasrc/router
a upravte obsah na následující:
import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";
import Event from "../views/Event.vue";
Vue.use(VueRouter);
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/event/:id",
name: "Event",
component: Event,
}
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes,
});
export default router;
Ruční úprava backendu Strapi
Jedna z hlavních výhod Strapi
je, že nám umožňuje upravovat ovladače, služby a další.
V této části upravíme ticket controller
v našem Strapi
backend. Při vytváření nového tiketu chceme provést určitou logiku, například:
- Kontrola, zda jsou dostupné vstupenky na akci dostatečné na vytvoření nových vstupenek.
- Kontrola, zda nebyly vyčerpány dostupné vstupenky na akci.
Chcete-li upravit ticket controller
, postupujte podle následujících kroků :
- Otevřete
strapi
složku ve vašem oblíbeném editoru kódu. - Přejděte na
src/api/ticket
složka. - Pod
src/api/ticket
klikněte na ovladače. - Otevřete
ticket.js
. - Nakonec aktualizujte obsah
ticket.js
obsahovat následující kód:
'use strict';
/**
* ticket controller
*/
const { createCoreController } = require('@strapi/strapi').factories;
module.exports = createCoreController('api::ticket.ticket', ({ strapi }) => ({
async create(ctx) {
const event_id = Number(ctx.request.body.data.event)
// some logic here
const event = await strapi.service('api::event.event').findOne(event_id, {
populate: "tickets"
})
if(ctx.request.body.data.total_seats > event.tickets_available) {
return ctx.badRequest('Cannot book ticket at the moment')
}
const response = await strapi.service('api::ticket.ticket').create(ctx.request.body)
await strapi.service('api::event.event').update(event_id, { data: {
tickets_available: event.tickets_available - ctx.request.body.data.total_seats
}})
return response;
}
}));
Závěr
Doufám, že vám tento tutoriál poskytl přehled o tom, jak vytvořit systém prodeje vstupenek s Strapi
. Je toho mnohem víc, co byste mohli do této aplikace přidat, myslete na to jako na výchozí bod.
- Rozhraní frontend repo pro tento výukový program lze nalézt zde.
- Backend repo pro tento výukový program lze nalézt zde.