Directus+Astro搭建博客网站

在本文开始之前你应该需要安装好DirectusAstro,若需帮助可参考:

Directus中需要建立一个名为articles的模型,并添加titlebody及图片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>

Post Comment