Nuxt Content 進階設定與踩坑紀錄【2】
這一篇是 Nuxt Content 系列的最後一章,主要分享進階設定與實際開發時踩過的坑。如果你有客製內容樣式或正在評估是否使用 Docus,希望這些經驗能對你有所幫助。
前言
上篇完成了內容顯示與互動功能後,這一篇則聚焦在後期優化與實務設定,包含樣式客製、資料結構設計,最後也會分享我對nuxt-content/Docus的一些使用心得,作為 Nuxt Content 系列的收尾。
標籤元件的客製調整
文章內容除了使用 TailwindCSS 做樣式調整,在某些標籤想要客製功能與新增區塊,官方有提供標籤元件調整,能夠覆蓋掉原本標籤,我針對圖片與多行程式碼標籤做客製調整,接下來我分別說明:
圖片標籤
圖片呈現後顯示原生 <img> 標籤,在觀看上無法放大,也沒有圖片說明,這也是我想動此元件原因,在開始之前造著官方建立在 components/content 裡面放 ProseImg.vue 元件。
元件裡面先放上官方 github 元件原始碼在開始客製。
- 新增圖片與說明:清空原始碼,新增
<img>與<span>並且 props 參數傳遞上去
<template>
<div class="my-5">
<img :src :alt :width :height class="pb-2 mb-0 mt-0" />
<span v-if="alt" class="font-bold pl-2">{{ alt }}</span>
</div>
</template>
- 顯示圖片 Lightbox:新增點擊開啟事件,Lightbox 傳遞到 body 同層,Lightbox 顯示放大圖片與說明
<template>
<div @click="isOpenModal = true" class="my-5">
<!-- ...略 (圖片與説明) -->
</div>
<Teleport to="body">
<div v-if="isOpenModal" @click="isOpenModal = false"
class="fixed inset-0 z-200 bg-black/60 flex items-center justify-center p-4">
<div @click.stop class="relative max-w-9/10 lg:max-w-200 max-h-full">
<!-- ...略 (Lightbox 內容) -->
</div>
</div>
</Teleport>
</template>
<script setup lang="ts">
const isOpenModal = ref(false);
</script>
多行程式碼標籤
呈現顯示高光的多行 code,這部分可能在撰寫技術文上,會不知道這段撰寫語言與檔案,並且複製程式碼需要自己選擇範圍,所以針對這部分我新增程式碼標題與一鍵複製,在 components 新增 ProsePre.vue 元件。
- 程式碼標題 : 檔名顯示 props 的
filename傳遞上去 ; 撰寫語言標示以 props 的language比對自訂列表顯示 Icon。
<template>
<div class="my-6 relative group">
<div v-if="language !== 'plaintext'"
class="p-2 h-10 bg-white rounded-t border border-gray-300 dark:bg-neutral-900 flex items-center ">
<Icon v-if="language && LanguageIconList[language]" :name="LanguageIconList[language]" class="align-middle" />
<span class="pl-2">{{ filename }}</span>
</div>
<!-- ...略 (code) -->
</div>
</template>
<script setup lang="ts">
type LanguageIconKey = keyof typeof LanguageIconList;
// 圖示 https://icon-sets.iconify.design/
const LanguageIconList = {
plaintext: '',
javascript: 'logos:javascript',
typescript: 'logos:typescript-icon',
...
} as const;
</script>
- 一鍵複製 :
<template>設定按鈕點擊觸發事件,事件上使用原生 Clipboard API 提供writeText()方法結果會回傳 Promise,此外設定時間內防止二次點擊機制。
<script setup lang="ts">
const isCopied = ref(false);
const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(props.code);
isCopied.value = true;
// 2秒後重置按鈕狀態
setTimeout(() => {
isCopied.value = false;
}, 2000);
} catch (err) {
console.error('無法複製內容: ', err);
}
};
</script>
JSON 資料集合設計與取用
Nuxt content 除了 Markdown 還有JSON、YAML、CSV 可以內容管理,在我的個人網站有使用到 JSON 管理我的作品,主要在列表與內頁上不需要像文章需要撰寫段落與標題,所以選擇我熟悉 JSON 檔新增作品欄位,在 content/works 裡面放置每個作品檔案,方便後續管理。
與上篇一樣先依照官方文件完成集合設定 。要注意 type 的值為 data
作品列表
取資料並進行最新篩選,與上篇一樣使用官方 queryCollection 裡 .all() 取所有資料,再篩選欄位與排序
const { data: works, pending } = await useAsyncData(
"works-data",
async () => {
const data = await queryCollection("works").all();
//...略 (篩選新增 sortIndex 與 slug)
return processedWorks.sort((a, b) => Number(b.sortIndex) - Number(a.sortIndex));
},
);
作品內頁
取單一特定資料時,透過 queryCollection 搭配 .where().first() 取得符合條件的第一筆資料。
由於 stem 會以檔案設計 works/1.xxx 帶排序前綴,而我在內頁路徑篩選掉順序,因此在查詢時改用模糊比對尾端的 slug取得文章。
export const useWorkDetail = (path: string, slug: string) => {
return useAsyncData(path, () => {
return queryCollection("works").where("stem", "LIKE", `%${slug}`).first();
});
};
與上面內容一樣放在 composables ,解決 middleware 與頁面初始化。
使用 Docus 強化內容呈現
Docus 是 Nuxt Content 團隊開發的套件,用來美化 Markdown 內頁,並結合 Nuxt UI 快速套版,支援客製化調整。
我在個人網站沒有使用 Docus,原因是希望元件與內容呈現保留彈性。如果日後改用資料庫,現有元件仍能復用;不過相對地使用 Content 會花比較多時間在設計元件與切版上。
如果想要快速生成一個技術文件、教學網站,很適合使用。
Docus 範例 (目前只做到基本專案初始化,Docus 本身有提供不少初始元件與版型,可以再依需求調整)
總結
這兩篇文章在分享我在個人網站使用 Nuxt Content 的核心做法與實作心得。 如果你對 Nuxt Content 某些區塊或寫法有疑問,歡迎透過留言板與我交流~
下一篇我會帶大家了解網站留言板,我為什麼選擇 Disqus、怎麼載入專案,以及在實作過程遇到的問題。有興趣的話可以繼續往下看!(待我上稿 XD)