Files
website-enchun-mgr/apps/frontend/src/pages/news.astro
pkupuk 9c2181f743 feat(frontend): update pages, components and branding
Refresh Astro frontend implementation including new pages (Portfolio, Teams, Services), components, and styling updates.
2026-02-11 11:50:42 +08:00

290 lines
6.9 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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