Landing Page

When using Kecare as a blog, we need a landing page (i.e., the homepage) to display the list of blog articles.

Landing Page Template

The landing page template is a .list.ts file, stored in the .kecare/ directory. The generator will call it after processing all articles to generate the article list page.

A Runnable Template

Create file .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;
}

In summary, .list.ts only requires you to return an array containing fsPath and template. The generator can then correctly call the template for page generation. Because pagination design is needed, theme authors must write the pagination logic here. The array can also contain a single item (if you are not implementing pagination design, you can directly return fsPath: xxx, template: xxx).

Page Components

We have called <Landing/> in the template, so next we should proceed to writing the page component.

~/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>

In Vue, the way to use data is through text interpolation, like <span>Article Title: {{ article.frontMatter.title }}</span>. It's quite simple, and that's pretty much it. As for the specific styling, you'll have to use your imagination.

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