Directus+Astro搭建博客网站
在本文开始之前你应该需要安装好Directus和Astro,若需帮助可参考:
Directus中需要建立一个名为articles
的模型,并添加title
、body
及图片cover_image
字段内容。
安装Directus SDK
为Astro安装Directus的JavaScript SDK,有了SDK后基于API的CRUD各种操作将变得非常简单
npm install @directus/sdk
配置Directus令牌
为了Api的安全考虑,我们需要在Directus后台配置一个被Astro使用的验证账号
或者STATIC_TOKEN
打开directus后台,找到用户管理并添加一个用于API的认证的账号
#Option 为用户添加Token
个人更倾向于此方式,由于账号密码需要明文配置,所以Token更加安全,需要注意的是directus用户的token是一次性生成的且不可见的,所以我们生成出来token字符串的时候一定要复制下来
为Astro配置Directus SDK接口
在Astro目录下创建.env
环境配置文件,根据实际情况修改
#Directus地址 PUBLIC_DIRECTUS_URL=http://localhost:8055 #认证账号 DIRECTUS_EMAIL= #认证密码 DIRECTUS_PASSWORD= #账号Token DIRECTUS_STATIC_TOKEN=
注意这里的账号密码和Token二选一
填写,个人喜欢用token。
创建文件 /src/utils/get-asset-url.js
export function getAssetURL(id) { if (!id) return null; return `${import.meta.env.PUBLIC_DIRECTUS_URL}/assets/${id}`; }
创建文件 /src/utils/get-directus-client.js
import { Directus } from "@directus/sdk"; export const getDirectusClient = async () => { const directus = new Directus(import.meta.env.PUBLIC_DIRECTUS_URL); if (directus.auth.token) return directus; if (import.meta.env.DIRECTUS_EMAIL && import.meta.env.DIRECTUS_PASSWORD) { await directus.auth.login({ email: import.meta.env.DIRECTUS_EMAIL, password: import.meta.env.DIRECTUS_PASSWORD, }); } else if (import.meta.env.DIRECTUS_STATIC_TOKEN) { await directus.auth.static(import.meta.env.DIRECTUS_STATIC_TOKEN); } return directus; };
这两个文件用于读取.env
的配置信息。
创建文件 /src/utils/format-relative-time.js
,计算相对时间的函数
const UNITS = { year: 24 * 60 * 60 * 1000 * 365, month: (24 * 60 * 60 * 1000 * 365) / 12, day: 24 * 60 * 60 * 1000, hour: 60 * 60 * 1000, minute: 60 * 1000, second: 1000, }; const LOCALE = "en"; const rtf = new Intl.RelativeTimeFormat(LOCALE, { numeric: "auto" }); export function formatRelativeTime(fromDate, toDate) { const elapsed = fromDate - (toDate || new Date()); // "Math.abs" accounts for both "past" & "future" scenarios for (let u in UNITS) { if (Math.abs(elapsed) > UNITS[u] || u === "second") return rtf.format(Math.round(elapsed / UNITS[u]), u); } return fromDate.toLocaleDateString(LOCALE); }
内容展示组件
创建用于文章列表展示的component /src/components/Article.astro
--- import { getAssetURL } from '../utils/get-asset-url' const { article } = Astro.props --- <article> <div class="article__topWrapper"> <div class="article__imageWrapper"> <img src={getAssetURL(article.cover_image)} alt="" loading="lazy" /> </div> <span aria-hidden="true" class="tag">Writing</span> </div> <div class="article__bottomWrapper"> <h1 class="article__title"> <a href={`/articles/${article.id}`}> {article.title} </a> </h1> <div class="article__detail"> <div class="article__detailInner"> <div class="article__detailInnerTime">{article.publish_date}</div> <div class="article__detailInnerCategory">Writing</div> </div> </div> </div> </article>
在/src/pages/index.astro
调用文章列表
--- import Layout from '../layouts/Layout.astro'; import Article from '../components/Article.astro' import { getDirectusClient } from '../utils/get-directus-client' import { formatRelativeTime } from '../utils/format-relative-time' const directus = await getDirectusClient() const response = await directus.items("articles").readByQuery({ fields: ["*"] }) const formattedArticles = response.data.map((article) => { return { ...article, date_created: formatRelativeTime(new Date(article.date_created)), } }) const [ ...articles] = formattedArticles --- <Layout title="BLOG"> <main> <section class="main-content"> <div class="container"> <div class="articles-grid"> {articles.map((article) => ( <Article article={article} /> ))} </div> </div> </section> </main> </Layout>
文章展示页 /src/pages/articles/[id].astro
--- import Layout from '../../layouts/Layout.astro'; import MoreArticles from '../../components/MoreArticles.astro' import { getDirectusClient } from '../../utils/get-directus-client' import { getAssetURL } from '../../utils/get-asset-url' import { formatRelativeTime } from '../../utils/format-relative-time' export async function getStaticPaths() { const directus = await getDirectusClient(); const response = await directus.items("articles").readByQuery({ fields: ["*"], filter: { status: { _eq: "published" } }, limit: -1, }) let articles = response.data for (const [index, article] of Object.entries(articles)) { const moreArticlesResponse = await directus.items("item").readByQuery({ fields: ["*"], filter: { _and: [{ id: { _neq: article.id } }, { status: { _eq: "published" } }], }, limit: 2, }) const formattedMoreArticles = moreArticlesResponse.data.map( (formattedMoreArticle) => { return { ...formattedMoreArticle, publish_date: formatRelativeTime( new Date(formattedMoreArticle.date_created) ), } } ) articles[index] = { params: { id: article.id.toString() }, props: { article: { ...article, publish_date: formatRelativeTime(new Date(article.date_created)), moreArticles: formattedMoreArticles, } } } } return articles } const { article } = Astro.props --- <Layout title="{article.title}"> <div class="current-article"> <section> <div class="container"> <a href="/" class="current-article__backlink"> <span>Back to Articles</span> </a> <h1 class="current-article__title">{article.title}</h1> <div class="current-article__detail"> <div class="current-article__wrapperOuter"> <div class="current-article__wrapperInner"> <div> <div class="current-article__time"> {article.date_created} </div> </div> </div> </div> <div class="current-article_coverImage"> <img src={getAssetURL(article.cover_image)} alt="" /> </div> </div> <div class="current-article__body"> <div class="current-article__bodyContent" set:html={article.body}></div> </div> </div> </section> <MoreArticles articles={article.moreArticles} /> </div> </Layout>
更多文章组件 /src/components/MoreArticles.astro
--- import Article from '../components/Article.astro' const { articles } = Astro.props --- <section class="more-articles"> <div class="container"> <h1 class="more-articles__title">More Articles</h1> {articles?.length !== 0 && ( <div class="articles-grid"> {articles.map((article, index) => ( <Article article={article} bordered={index !==articles.length - 1} /> ))} </div> )} </div> </section>