落地页
当将 Kecare 用作博客时,我们需要一个落地页(即首页),用于展示博客的文章列表。
落地页模板
落地页模板是一个 .list.ts 文件,存放在 .kecare/ 目录下。生成器会在处理完所有文章后调用它,用于生成文章列表页面。
一个可运行模板
创建文件 .kecare/foo-bar.list.ts:
import { join } from 'node:path';
import type { ArticlesRecord, KecareContext } from "kecare";
// 标识这是一个文章详情类型的模板
export const type = 'article-detail';
// 每页显示的文章数量
const ARTICLES_PER_PAGE = 5;
// 目标语言
const TARGET_LANGUAGE = 'zh-CN';
export function generator(context: KecareContext, articles: ArticlesRecord) {
// 存储生成的文件
const files: Array<{ fsPath: string, template: string }> = [];
// 收集目标语言的文章
const zhArticles: Array<any> = [];
// 遍历所有文章,筛选目标语言
for (const articleHash in articles) {
const articleLanguages = articles[articleHash]!;
if (articleLanguages[TARGET_LANGUAGE]) {
zhArticles.push(articleLanguages[TARGET_LANGUAGE]);
}
}
// 按日期排序(最新在前)
zhArticles.sort((a, b) => {
return new Date(b.frontMatter.date).getTime() - new Date(a.frontMatter.date).getTime();
});
// 计算分页
const totalArticles = zhArticles.length;
const totalPages = Math.ceil(zhArticles.length / ARTICLES_PER_PAGE);
// 为每一页生成对应的 Vue 文件
for (let pageIndex = 0; pageIndex < totalPages; pageIndex++) {
const startIndex = pageIndex * ARTICLES_PER_PAGE;
const endIndex = startIndex + ARTICLES_PER_PAGE;
const pageArticles = zhArticles.slice(startIndex, endIndex);
const pageNumber = pageIndex + 1;
// 第一页为 index.vue,后续为 page-2.vue, page-3.vue...
const fileName = pageNumber === 1 ? 'index.vue' : `page-${pageNumber}.vue`;
files.push({
fsPath: join(context.projectPath, 'app', 'pages', fileName),
template: `
<script setup lang="ts">
// 引入落地页组件
import Landing from "~/components/landing.vue";
// 设置页面标题
useHead({ title: '首页' });
// 当前页的文章数据
const articles = ${JSON.stringify(pageArticles, null, 2)};
// 当前页码
const currentPage = ${pageNumber};
// 总页数
const totalPages = ${totalPages};
// 文章总数
const totalArticles = ${totalArticles};
</script>
<template>
<Landing
:articles="articles"
:current-page="currentPage"
:total-pages="totalPages"
:total-articles="totalArticles"
/>
</template>
`,
});
}
return files;
}
总而言之,.list.ts 仅需你返回一个数组中包含 fsPath 和 template 即可,生成器就能正确调用模板进行页面生成,因为需要进行分页设计,所以需要主题作者在这里进行分页逻辑的编写。数组中也可以是一个项(如果你不进行分页设计,可直接return fsPath: xxx , template: xxx)
页面组件
我们在模板中调用了 <Landing/> 所以接下来我们就应该到了页面组件的编写
~/components/landing.vue
<script lang='ts' setup>
//一个组件需要显式声明它所接受的 props,这样 Vue 才能知道外部传入的哪些是 props。使用defineProps来声明
const props = defineProps<{
articles: ArticleVariant;
currentPage?: number;
totalPages?: number;
totalArticles?: number;
}>();
</script>
// 一个首页文章卡片显示的例子
<template>
<NuxtLink
class="article-card"
v-for="(article, index) in articles"
:key="index"
:to="article.urlPath"
>
<img class="article-cover" :src="article.frontMatter.cover" />
<div class="article-content">
<div class="article-title">{{ article.frontMatter.title }}</div>
<div class="article-desc">{{ article.desc }}</div>
<div class="article-meta">
<span>作者: {{ article.frontMatter.author }}</span>
<span>{{ article.frontMatter.date }}</span>
</div>
</div>
</NuxtLink>
</template>
<style scoped>
</style>
在Vue中,使用数据的形式是文本插值即 <span> 文章标题: {{ article.frontMatter.title }}</span> 这种形式,很简单吧,大概也就这些吧。具体样式就要你展开想象了