Создание блога с помощью Vue 3 + Tailwindcss + Supabase

Здравствуйте, ребята,

Я разрабатываю веб-приложения на Vue уже 7 лет, и мне это очень нравится!

Vue 3 только что вышел, и мне показалось интересным написать небольшой пост о том, как создать свое первое приложение Vue 3 с помощью Tailwindcss.

1. Установите Vue.js

Предварительные условия: установите Node.js версии 15.0 или выше.

Используйте CLI для создания приложения:

npm init vue@latest
Войдите в полноэкранный режим Выйдите из полноэкранного режима

Затем:

✔ Project name: … <your-project-name>
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit testing? … No / Yes
✔ Add Cypress for both Unit and End-to-End testing? … No / Yes
✔ Add ESLint for code quality? … No / Yes
✔ Add Prettier for code formatting? … No / Yes

Scaffolding project in ./<your-project-name>...
Done.
Войдите в полноэкранный режим Выйти из полноэкранного режима

Установите supabase:

npm i @supabase/supabase-js
Войти в полноэкранный режим Выйти из полноэкранного режима

Наконец-то:

npm i && npm run dev
Войти в полноэкранный режим Выйдите из полноэкранного режима

Теперь ваш проект должен быть запущен.

2. Установите Tailwindcss

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

3. Очистите ваши представления и компоненты

По умолчанию у вас есть компоненты и представления. Удалите их и вместо них создайте эти файлы:

src/views/Home.vue
src/views/Post.vue
src/components/Header.vue
src/store.js
src/supabase.js
Войти в полноэкранный режим Выйти из полноэкранного режима

Вы можете сразу же изменить свой компонент Header.vue на:

<template>
  <header class=" text-center my-8">
    <h1 class="text-blue-300 text-6xl font-bold mb-4" @click="$router.push('/')">Guillaume's blog</h1>
    <p class="text-xl text-slate-500">A blog with posts on what I like.</p>
  </header>
</template>
Войти в полноэкранный режим Выйти из полноэкранного режима

4. Vue-маршрутизатор из App.vue

Здесь нам нужен корневой файл (App.vue), который отображает наши маршруты.

<script setup>
import { RouterView } from 'vue-router'
import Header from './components/Header.vue'
</script>

<template>
  <Header />
  <RouterView />
</template>
Войти в полноэкранный режим Выход из полноэкранного режима

Также нам нужен наш маршрутизатор для отображения этих страниц:

// src/router/index.js

import { createRouter, createWebHistory } from 'vue-router'


const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'Home',
      component: () => import('../views/Home.vue')
    }, {
      path: '/post/:id',
      name: 'Post',
      component: () => import('../views/Post.vue')
    }
  ]
})

export default router

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

Теперь у вас должно быть доступно 2 маршрута.

5. Настройте Supabase

Пожалуйста, посмотрите мое видео, чтобы создать таблицу «Posts» и настроить Supabase.

Перейдите на supabase.js и настройте ваш клиент:

import { createClient } from '@supabase/supabase-js';

const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;

const supabase = createClient(supabaseUrl, supabaseAnonKey);

export default supabase
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь вы должны создать файл .env:

touch .env

VITE_SUPABASE_URL=
VITE_SUPABASE_ANON_KEY=
Войти в полноэкранный режим Выйти из полноэкранного режима

Пожалуйста, перезапустите ваш сервер.

6. Давайте создадим магазин.

В store.js создадим реактивную константу. Реактивная константа важна здесь для того, чтобы сделать вашу переменную динамичной и отображать элементы. В противном случае в вашем приложении данные останутся пустыми.

import { reactive } from 'vue'

const store = reactive({
  posts: []
})

export {store};
Вход в полноэкранный режим Выход из полноэкранного режима

7. Дом — это дом

Наше представление Home.vue отображает список постов, извлеченных из Supabase.

Для этого мы создаем функцию fetchPosts, срабатывающую сразу при монтировании.

Если постов нет, появляется сообщение: «нет постов».

<script setup>
import { store } from '../store'
import supabase from '../supabase'

const getWordsNumber = (str) => (str.split(' ').length)

const fetchPosts = async () => {
  let { data: posts, error } = await supabase
  .from('posts')
  .select()

  if (error) throw new Error(error)

  store.posts = posts
} 

fetchPosts()
</script>

<template>
  <div class="Home">
    <main class="container mx-auto">
      <div v-if="store.posts.length < 1">
        There is no posts.
      </div>
      <div v-else>
        <div class="PostItem border border-slate-200 mb-4 p-4 rounded-lg cursor-pointer" v-for="item, itemIndex in store.posts" :key="itemIndex" @click="$router.push(`/post/${item.id}`)">
          <h1 class="text-slate-900 text-3xl font-bold">
            {{ item.title }}
          </h1>
          <p>{{ getWordsNumber(item.description) }} words.</p>
        </div>
      </div>
    </main>
  </div>
</template>
Вход в полноэкранный режим Выход из полноэкранного режима

Вы можете видеть, что при нажатии мы переходим к ранее созданному маршруту постов.

8. Вид постов

Наш вид поста немного отличается.

Нам также нужно получить пост из Supabase, иначе наши данные будут пустыми.

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

<script setup>
import { reactive } from 'vue'
import { useRoute } from 'vue-router'
import {store} from '../store'
import supabase from '../supabase'

const route = useRoute()

let post = reactive({})

const fetchPost = async (id) => {
  const found = store.posts.find(x => x.id === parseInt(route.params.id))
  if (found) {
    Object.assign(post, found)
    return
  }
  let { data, error } = await supabase
  .from('posts')
  .select()
  .eq('id', id)
  .single()

  if (error) throw new Error(error)

  Object.assign(post, data)
} 

fetchPost(route.params.id)
</script>

<template>
  <div class="Post text-center container mx-auto">
    <div v-if="!post">
      No post found.
    </div>
    <div v-else>
      <h1 class="text-slate-900 text-3xl font-bold mb-4">{{ post.title }}</h1>
      <p class="text-md text-slate-300">{{ post.created_at }}</p>
      <p class="text-xl text-slate-500">{{ post.description}}</p>
    </div>
  </div>
</template>
Вход в полноэкранный режим Выход из полноэкранного режима

Надеюсь, вам понравилась эта небольшая статья, если у вас есть вопросы или лучшие практики, пожалуйста, обращайтесь.

Всего наилучшего!

Гийом

Оцените статью
devanswers.ru
Добавить комментарий