Reduces duplication across marketing pages by converting sections into reusable components like CtaSection and HeaderBg. Consolidates styling patterns to improve maintainability and consistency of the user interface.
127 lines
4.8 KiB
Plaintext
127 lines
4.8 KiB
Plaintext
---
|
||
/**
|
||
* News/Blog Listing Page - 行銷放大鏡
|
||
* URL: /news
|
||
* Displays all published blog posts from Payload CMS
|
||
*/
|
||
import Layout from '../layouts/Layout.astro'
|
||
import ArticleCard from '../components/blog/ArticleCard.astro'
|
||
import CategoryFilter from '../components/blog/CategoryFilter.astro'
|
||
import { fetchPosts, fetchCategories } from '../lib/api/blog'
|
||
|
||
// Metadata for SEO
|
||
const title = '行銷放大鏡 | 恩群數位行銷'
|
||
const description = '閱讀恩群數位的專業行銷文章,掌握最新的數位行銷趨勢、社群經營技巧、Google 廣告策略等實用內容。'
|
||
|
||
// Pagination settings
|
||
const PAGE_SIZE = 12
|
||
|
||
// Get query parameters
|
||
const url = Astro.url
|
||
const pageParam = url.searchParams.get('page')
|
||
const page = Math.max(1, parseInt(pageParam || '1'))
|
||
|
||
// Fetch posts from Payload CMS
|
||
const postsData = await fetchPosts(page, PAGE_SIZE)
|
||
const posts = postsData.docs
|
||
const totalPages = postsData.totalPages
|
||
const hasPreviousPage = postsData.hasPreviousPage
|
||
const hasNextPage = postsData.hasNextPage
|
||
|
||
// Fetch categories
|
||
const categories = await fetchCategories()
|
||
|
||
// Filter out categories without slugs and the legacy "文章分類" container
|
||
const validCategories = categories.filter(c =>
|
||
c.slug && c.slug !== 'wen-zhang-fen-lei'
|
||
)
|
||
---
|
||
|
||
<Layout title={title} description={description}>
|
||
<!-- Blog Hero Section -->
|
||
<section class="py-[60px] px-5 bg-white lg:py-10 md:py-10 md:px-4" aria-labelledby="blog-heading">
|
||
<div class="max-w-[1200px] mx-auto">
|
||
<div class="flex items-center justify-center gap-4 md:flex-wrap md:gap-3">
|
||
<div class="w-10 h-0.5 bg-link-hover md:w-[30px]"></div>
|
||
<div class="text-center">
|
||
<h2 id="blog-heading" class="text-2xl font-bold text-link-hover mb-2 lg:text-[1.75rem] md:text-[1.5rem]">行銷放大鏡</h2>
|
||
<p class="text-base text-slate-600 font-accent md:text-[0.9375rem]">Marketing Blog</p>
|
||
</div>
|
||
<div class="w-10 h-0.5 bg-link-hover md:w-[30px]"></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Category Filter -->
|
||
<section class="bg-white pb-5">
|
||
<div class="max-w-[1200px] mx-auto">
|
||
<CategoryFilter categories={validCategories} />
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Blog Posts Grid -->
|
||
<section class="py-10 px-5 pb-[60px] bg-slate-100 min-h-[60vh] md:py-[30px] md:px-4 md:pb-[50px]" aria-label="文章列表">
|
||
<div class="max-w-[1200px] mx-auto">
|
||
{
|
||
posts.length > 0 ? (
|
||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-10 w-full max-w-[1200px] mx-auto">
|
||
{posts.map((post) => (
|
||
<ArticleCard post={post} />
|
||
))}
|
||
</div>
|
||
) : (
|
||
<div class="text-center py-20 px-5">
|
||
<p class="text-lg text-slate-500">暫無文章</p>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
<!-- Pagination -->
|
||
{
|
||
totalPages > 1 && (
|
||
<nav class="pt-5" aria-label="分頁導航">
|
||
<div class="flex items-center justify-center gap-6 md:gap-4">
|
||
{
|
||
hasPreviousPage && (
|
||
<a
|
||
href={`?page=${page - 1}`}
|
||
class="inline-flex items-center gap-2 px-5 py-[10px] bg-white border-2 border-slate-300 rounded-md text-slate-700 text-[0.9375rem] font-medium no-underline transition-all duration-[250ms] hover:border-link-hover hover:text-link-hover hover:bg-[rgba(35,96,140,0.05)] md:px-4 md:py-2 md:text-[0.875rem]"
|
||
aria-label="上一頁"
|
||
>
|
||
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
|
||
<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>
|
||
</svg>
|
||
上一頁
|
||
</a>
|
||
)
|
||
}
|
||
|
||
<div class="flex items-center gap-2 font-accent text-base text-slate-600">
|
||
<span class="font-bold text-link-hover">{page}</span>
|
||
<span class="text-slate-400">/</span>
|
||
<span>{totalPages}</span>
|
||
</div>
|
||
|
||
{
|
||
hasNextPage && (
|
||
<a
|
||
href={`?page=${page + 1}`}
|
||
class="inline-flex items-center gap-2 px-5 py-[10px] bg-white border-2 border-slate-300 rounded-md text-slate-700 text-[0.9375rem] font-medium no-underline transition-all duration-[250ms] hover:border-link-hover hover:text-link-hover hover:bg-[rgba(35,96,140,0.05)] md:px-4 md:py-2 md:text-[0.875rem]"
|
||
aria-label="下一頁"
|
||
>
|
||
下一頁
|
||
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
|
||
<path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/>
|
||
</svg>
|
||
</a>
|
||
)
|
||
}
|
||
</div>
|
||
</nav>
|
||
)
|
||
}
|
||
</div>
|
||
</section>
|
||
</Layout>
|
||
|