Refresh Astro frontend implementation including new pages (Portfolio, Teams, Services), components, and styling updates.
290 lines
6.9 KiB
Plaintext
290 lines
6.9 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="blog-hero" aria-labelledby="blog-heading">
|
||
<div class="container">
|
||
<div class="section_header_w_line">
|
||
<div class="divider_line"></div>
|
||
<div class="header_subtitle">
|
||
<h2 id="blog-heading" class="header_subtitle_head">行銷放大鏡</h2>
|
||
<p class="header_subtitle_paragraph">Marketing Blog</p>
|
||
</div>
|
||
<div class="divider_line"></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Category Filter -->
|
||
<section class="filter-section">
|
||
<div class="container">
|
||
<CategoryFilter categories={validCategories} />
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Blog Posts Grid -->
|
||
<section class="blog-section" aria-label="文章列表">
|
||
<div class="container">
|
||
{
|
||
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="empty-state">
|
||
<p class="empty-text">暫無文章</p>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
<!-- Pagination -->
|
||
{
|
||
totalPages > 1 && (
|
||
<nav class="pagination" aria-label="分頁導航">
|
||
<div class="pagination-container">
|
||
{
|
||
hasPreviousPage && (
|
||
<a
|
||
href={`?page=${page - 1}`}
|
||
class="pagination-link pagination-link-prev"
|
||
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="pagination-info">
|
||
<span class="current-page">{page}</span>
|
||
<span class="page-separator">/</span>
|
||
<span class="total-pages">{totalPages}</span>
|
||
</div>
|
||
|
||
{
|
||
hasNextPage && (
|
||
<a
|
||
href={`?page=${page + 1}`}
|
||
class="pagination-link pagination-link-next"
|
||
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>
|
||
|
||
<style>
|
||
/* Blog Hero Section */
|
||
.blog-hero {
|
||
padding: 60px 20px;
|
||
background-color: #ffffff;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.section_header_w_line {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 16px;
|
||
}
|
||
|
||
.header_subtitle {
|
||
text-align: center;
|
||
}
|
||
|
||
.header_subtitle_head {
|
||
color: var(--color-enchunblue, #23608c);
|
||
font-family: "Noto Sans TC", sans-serif;
|
||
font-weight: 700;
|
||
font-size: 2rem;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.header_subtitle_paragraph {
|
||
color: var(--color-gray-600, #666);
|
||
font-family: "Quicksand", "Noto Sans TC", sans-serif;
|
||
font-weight: 400;
|
||
font-size: 1rem;
|
||
}
|
||
|
||
.divider_line {
|
||
width: 40px;
|
||
height: 2px;
|
||
background-color: var(--color-enchunblue, #23608c);
|
||
}
|
||
|
||
/* Filter Section */
|
||
.filter-section {
|
||
background-color: #ffffff;
|
||
padding-bottom: 20px;
|
||
}
|
||
|
||
/* Blog Section */
|
||
.blog-section {
|
||
padding: 40px 20px 60px;
|
||
background-color: #f8f9fa;
|
||
min-height: 60vh;
|
||
}
|
||
|
||
/* Empty State */
|
||
.empty-state {
|
||
text-align: center;
|
||
padding: 80px 20px;
|
||
}
|
||
|
||
.empty-text {
|
||
font-size: 1.125rem;
|
||
color: var(--color-gray-500, #999);
|
||
}
|
||
|
||
/* Pagination */
|
||
.pagination {
|
||
padding-top: 20px;
|
||
}
|
||
|
||
.pagination-container {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 24px;
|
||
}
|
||
|
||
.pagination-link {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 10px 20px;
|
||
background: #ffffff;
|
||
border: 2px solid var(--color-gray-300, #e0e0e0);
|
||
border-radius: 8px;
|
||
color: var(--color-gray-700, #555);
|
||
font-family: "Noto Sans TC", sans-serif;
|
||
font-size: 0.9375rem;
|
||
font-weight: 500;
|
||
text-decoration: none;
|
||
transition: all 0.25s ease;
|
||
}
|
||
|
||
.pagination-link:hover {
|
||
border-color: var(--color-enchunblue, #23608c);
|
||
color: var(--color-enchunblue, #23608c);
|
||
background: rgba(35, 96, 140, 0.05);
|
||
}
|
||
|
||
.pagination-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-family: "Quicksand", sans-serif;
|
||
font-size: 1rem;
|
||
color: var(--color-gray-600, #666);
|
||
}
|
||
|
||
.current-page {
|
||
font-weight: 700;
|
||
color: var(--color-enchunblue, #23608c);
|
||
}
|
||
|
||
.page-separator {
|
||
color: var(--color-gray-400, #ccc);
|
||
}
|
||
|
||
/* Responsive Adjustments */
|
||
@media (max-width: 991px) {
|
||
.header_subtitle_head {
|
||
font-size: 1.75rem;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 767px) {
|
||
.blog-hero {
|
||
padding: 40px 16px;
|
||
}
|
||
|
||
.section_header_w_line {
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
}
|
||
|
||
.divider_line {
|
||
width: 30px;
|
||
}
|
||
|
||
.blog-section {
|
||
padding: 30px 16px 50px;
|
||
}
|
||
|
||
.pagination-container {
|
||
gap: 16px;
|
||
}
|
||
|
||
.pagination-link {
|
||
padding: 8px 16px;
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.header_subtitle_head {
|
||
font-size: 1.5rem;
|
||
}
|
||
|
||
.header_subtitle_paragraph {
|
||
font-size: 0.9375rem;
|
||
}
|
||
}
|
||
</style>
|