iT邦幫忙

2022 iThome 鐵人賽

DAY 23
0
Modern Web

Nuxt 3 學習筆記系列 第 23

[Day 23] Nuxt 3 實作部落格 - 頁面的導航守衛與切換效果

  • 分享至 

  • xImage
  •  

前言

前一篇,我們完成了新增文章的頁面與流程,在網站開發的過程中,有些頁面是具有瀏覽的限制,例如,我們不希望一般的使用者能進到管理者頁面專用的頁面進行操作,這時候我們就需要做一些權限的驗證與限制,在 Nuxt 的頁面提供了路由中間件可以在我們導航至頁面之前,執行一些處理函數,就實作導航守衛 (Navigations Guards) 的效果。最後會介紹一下在 Nuxt 3 所提供的頁面和布局切換時的進度條 (Progress bar)轉場效果 (Transitions)

頁面間的導航守衛

只允許已登入使用者新增文章

首先,我們新增一個路由中間件 ./middleware/manage-auth.js 內容如下:

import { useUserStore } from '@/stores/user'

export default defineNuxtRouteMiddleware(() => {
  if (process.client) {
    const userStore = useUserStore()

    if (!userStore.profile?.id) {
      return navigateTo('/login')
    }
  } else {
    return navigateTo('/')
  }
})

在頁面元件中使用 definePageMeta() 來套用 manage-auth 中間件。調整 ./pages/manage/articles/create.vue 內容:

<script setup>
// ...

definePageMeta({
  middleware: 'manage-auth'
})
</script>

我們首頁新增一個可以導向至 /manage/articles/create 頁面的按鈕。

<NuxtLink
  class="text-md mt-12 rounded-sm bg-emerald-500 py-2 px-4 font-medium text-white hover:bg-emerald-600 focus:outline-none focus:ring-2 focus:ring-emerald-400 focus:ring-offset-2"
  to="/manage/articles/create"
>
  前往撰寫文章
</NuxtLink>

當我們處於未登入的情況,點擊前往後,會經由中間件判斷 process.client 是否是在客戶端,進而從 User Store 取出使用者資訊進行判斷,當不存在 userStore.profile.id 時,表示未登入我們將導航至登入頁面 /login;當登入完成後,就可以使用按鈕成功導航至新增文章的頁面。如果process.clientfalse,表示導航是在伺服器端觸發的,例如,我們直接透過網址進入新增文章頁面,將會一律被重新導航至首頁。
https://i.imgur.com/03jiBt3.gif

登入完成後導回至登入前瀏覽的頁面

當使用者在瀏覽網站時被引導或準備登入時,我們可以將使用者目前的頁面進行記錄,以便登入完成後,可以重新導向至使用者登入前的頁面,以此提供使用者更棒的體驗。

首先,我們新增一個路由中間件 ./middleware/logged-in-redirect.js 內容如下:

export default defineNuxtRouteMiddleware((to, from) => {
  if (from && to.path !== from.path && !to.query.redirect_to) {
    let redirectTo = null
    if (from.query.redirect_to) {
      redirectTo = from.query.redirect_to
      from.query.redirect_to = undefined
    } else {
      redirectTo = from.fullPath
    }

    to.query.redirect_to = redirectTo

    return navigateTo(to)
  }
})

這個 logged-in-redirect 中間件的處理邏輯,我們接收 tofrom,分別為目標頁面與來源頁面,當使用者目標頁面 /login 還未帶上了 Query 參數 redirect_to,我們就將來源的完整路徑添加上去 from.fullPath,最後進行導向。

這裡需要進行判斷 redirect_to,否則會重複發生重新導向。

在登入頁面中使用 definePageMeta() 來套用 logged-in-redirect 中間件。調整 ./pages/login.vue 內容:

<script setup>
// ...

definePageMeta({
  middleware: 'logged-in-redirect'
})
</script>

在登入完成的地方,我們就可以使用 navigateTo() 導向至 redirect_to 給的頁面路徑。

<script setup>
const route = useRoute()

const handleEmailLogin = async () => {
  // ...

  navigateTo(route.query.redirect_to ?? '/')
}
</script>

完成後,我們從首頁點擊登入後.當登入完成後就會導向回首頁;而從文章頁面點擊登入,完成後則會導向回文章頁面。
https://i.imgur.com/7CxADfH.gif

頁面載入進度元件

Nuxt 3 提供一個 <NuxtLoadingIndicator> 元件,用作頁面導航後顯示載入的進度,會在頁面上方有一個進度條 (Progress bar)。

只需要將 <NuxtLoadingIndicator> 元件添加至 app.vue 或布局之中,調整 app.vue 內容如下:

<template>
  <NuxtLayout>
    <NuxtLoadingIndicator />
    <NuxtPage />
  </NuxtLayout>
</template>

元件可以傳入的屬性 (Props) 如下:

  • color: 進度條的顏色,可以傳入CSS 支援的色碼repeating-linear-gradient() 函数,預設為 repeating-linear-gradient(to right,#00dc82 0%,#34cdfe 50%,#0047e1 100%)
  • height: 進度條的高度數值,單位: px,預設值為 3
  • duration: 進度條載入的持續時間,單位: 毫秒,預設值為2000
  • throttle: 進度條的隱藏與顯示,限制在特定時間內僅觸發一次,單位: 毫秒,預設值為200

當我們從首頁切換頁面時,網頁上方就會出現一個進度條,表示頁面正在載入中。
https://i.imgur.com/oE2ONOW.gif

頁面切換的轉場效果

除了載入的進度條,頁面切換之間,也可以使用轉場效果 (Transitions) 來讓頁面之間的銜接更柔順,Nuxt 利用了 Vue 內建的 <Transition> 元件來幫助處理轉場和動畫,用以響應不斷變化的頁面與狀態。

頁面的轉場效果

Nuxt 預設為所有頁面 (Pages) 都設置了轉場,如果要啟用,請將以下 CSS 添加至 app.vue 中。

<template>
  <NuxtPage />
</template>

<style>
.page-enter-active,
.page-leave-active {
  transition: all 0.4s;
}
.page-enter-from,
.page-leave-to {
  opacity: 0;
  filter: blur(1rem);
}
</style>

每個頁面的 pageTransition 預設屬性皆為 { name: 'page', mode: 'out-in' }namepage 也就對應了 CSS 類別的開頭;modein-out, out-indefault三種參數可選。

.[pageTransition.name]-enter-active,
.[pageTransition.name]-leave-active {
  transition: all 0.4s;
}
.[pageTransition.name]-enter-from,
.[pageTransition.name]-leave-to {
  opacity: 0;
  filter: blur(1rem);
}

套用好頁面的轉場,就會有切換頁面時有模糊的效果。
https://i.imgur.com/G4OVhVE.gif

自訂頁面的轉場

既然知道 pageTransitionname 會對應頁面轉場的 CSS 名稱,我們就可以來自定義更多轉場,讓不同頁面套用不同的效果。

例如在 app.vue 添加 rotate 為前綴的類別名稱 CSS。

<style>
/* ... */
.rotate-enter-active,
.rotate-leave-active {
  transition: all 0.4s;
}
.rotate-enter-from,
.rotate-leave-to {
  opacity: 0;
  transform: rotate3d(1, 1, 1, 15deg);
}
</style>

在頁面中使用 definePageMeta() 來設定 pageTransition.namerotate。調整 ./pages/login.vue 添加如下程式碼。

<script setup>
// ...

definePageMeta({
  pageTransition: {
    name: 'rotate'
  }
})
</script>

當頁面切換時,皆會使用預設的模糊轉場效果,當切換至登入頁面就會套用指定的 rotate 頁面轉場,而有旋轉的轉場效果。
https://i.imgur.com/8Ggc0Vi.gif

布局的轉場效果

Nuxt 同樣為所有布局 (Layouts) 都設置了轉場,如果要啟用,請將以下 CSS 添加至 app.vue 中。

.layout-enter-active,
.layout-leave-active {
  transition: all 0.4s;
}
.layout-enter-from,
.layout-leave-to {
  filter: grayscale(1);
}

建立 ./layouts/teal.vue 布局,程式碼如下:

<template>
  <div class="h-screen bg-teal-50">
    <slot />
  </div>
</template>

將登入頁面的布局套用 teal

<script setup>
// ...

definePageMeta({
  layout: 'teal'
})
</script>

建立 ./layouts/green.vue 布局,程式碼如下:

<template>
  <div class="h-screen bg-teal-50">
    <slot />
  </div>
</template>

將註冊頁面的布局套用 green

<script setup>
// ...

definePageMeta({
  layout: 'green'
})
</script>

在登入頁面與註冊頁面切換時,因為兩個頁面使用了不同的布局,布局的轉場效果,使背影顏色會有灰階效果的轉場。
https://i.imgur.com/oaw3THe.gif

你也可以像自訂頁面轉場一樣,來使用 definePageMeta() 設定 layoutTransition.name 屬性,來指定自訂的轉場效果。

<script setup>
definePageMeta({
  layout: 'green',
  layoutTransition: {
    name: 'slide-in'
  }
})
</script>

禁用轉場效果

頁面與布局的轉場效果,都可以透過 definePageMeta 來設定 pageTransitionlayoutTransitionfalse 來禁止套用轉場效果。

<script setup>
definePageMeta({
  pageTransition: false
  layoutTransition: false
})
</script>

全域預設的轉場效果

你也可以在 nuxt.config.ts 設置預設的頁面與轉場效果,例如:

export default defineNuxtConfig({
  pageTransition: {
    name: 'fade',
    mode: 'out-in' // default
  },
  layoutTransition: {
    name: 'slide',
    mode: 'out-in' // default
  }  
})

當然要將所有頁面與布局預設禁用也可以設置如下:

export default defineNuxtConfig({
  pageTransition: false,
  layoutTransition: false
})

pageTransitionlayoutTransition 接受的屬性可以參考 TransitionProps

元件屬性傳入 transition

app.vue 中使用 <NuxtPage /> 時,你可以將 TransitionProps 作為元件的 Props 來啟用全域預設的轉場效果。

<template>
  <div>
      <NuxtPage :transition="{
        name: 'bounce',
        mode: 'out-in'
      }" />
    </NuxtLayout>
  </div>
</template>

當使用此方法設定轉場效果時,就不能在頁面中使用 definePageMeta() 來覆蓋這裡的頁面轉場設置。

小結

這篇我們主要實作了導航守衛,來為特定頁面添加瀏覽的權限,我們除了使用客戶端的 User Store 驗證外,也可以搭配 Cookie 再後端進行驗證,甚至為每個請求解析使用者,並查詢資料庫是否具有權限瀏覽,以此來控制使用者瀏覽頁面的權限,除了前端的阻擋外,更重要的是後端 API 也需要搭配進行權限驗證,才能有要的防止網頁漏洞產生,否則可能會發生,使用者無權瀏覽新增或管理的頁面,但是可以透過打 API 來進行相關操作,這樣是非常危險的。最後,我們將頁面切換時設置了進度條與轉場效果,使得整體網站能提供使用者更好的操作體驗,更多的轉場設置,也可以參考官方文件


感謝大家的閱讀,這是我第一次參加 iThome 鐵人賽,請鞭小力一些,也歡迎大家給予建議 :)
如果對這個 Nuxt 3 系列感興趣,可以訂閱接收通知,也歡迎分享給喜歡或正在學習 Nuxt 3 的夥伴。

範例程式碼

參考資料


上一篇
[Day 22] Nuxt 3 實作部落格 - 導覽列模板與新增文章
下一篇
[Day 24] Nuxt 3 搜尋引擎最佳化 (SEO) 與 HTML Meta Tag
系列文
Nuxt 3 學習筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言