落地页

当将 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 仅需你返回一个数组中包含 fsPathtemplate 即可,生成器就能正确调用模板进行页面生成,因为需要进行分页设计,所以需要主题作者在这里进行分页逻辑的编写。数组中也可以是一个项(如果你不进行分页设计,可直接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> 这种形式,很简单吧,大概也就这些吧。具体样式就要你展开想象了

文章作者:
文章链接:kecare.me/articles/e52553ad
版权声明: 博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源