feat(frontend): update pages, components and branding
Refresh Astro frontend implementation including new pages (Portfolio, Teams, Services), components, and styling updates.
This commit is contained in:
@@ -1,61 +1,289 @@
|
||||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
/**
|
||||
* 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'
|
||||
|
||||
// Placeholder for blog posts - would fetch from CMS
|
||||
const posts = [
|
||||
{ slug: 'en-qun-shu-wei-zui-xin-gong-gao', title: '恩群數位最新公告', date: '2023-01-01' },
|
||||
{ slug: 'google-xiao-xue-tang', title: 'Google小學堂', date: '2023-01-02' },
|
||||
// Add more
|
||||
];
|
||||
// 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>
|
||||
<section class="news-section">
|
||||
<Layout title={title} description={description}>
|
||||
<!-- Blog Hero Section -->
|
||||
<section class="blog-hero" aria-labelledby="blog-heading">
|
||||
<div class="container">
|
||||
<h1>行銷放大鏡</h1>
|
||||
<div class="posts-grid">
|
||||
{posts.map(post => (
|
||||
<article class="post-card">
|
||||
<h2><a href={`/wen-zhang-fen-lei/${post.slug}`}>{post.title}</a></h2>
|
||||
<p>{post.date}</p>
|
||||
</article>
|
||||
))}
|
||||
<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>
|
||||
.news-section {
|
||||
padding: 40px 0;
|
||||
/* Blog Hero Section */
|
||||
.blog-hero {
|
||||
padding: 60px 20px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
h1 {
|
||||
|
||||
.section_header_w_line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.header_subtitle {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.posts-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
|
||||
.header_subtitle_head {
|
||||
color: var(--color-enchunblue, #23608c);
|
||||
font-family: "Noto Sans TC", sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 2rem;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.post-card {
|
||||
border: 1px solid #ddd;
|
||||
padding: 20px;
|
||||
|
||||
.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;
|
||||
}
|
||||
.post-card h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
.post-card a {
|
||||
color: var(--color-gray-700, #555);
|
||||
font-family: "Noto Sans TC", sans-serif;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
.post-card a:hover {
|
||||
color: #007bff;
|
||||
|
||||
.pagination-link:hover {
|
||||
border-color: var(--color-enchunblue, #23608c);
|
||||
color: var(--color-enchunblue, #23608c);
|
||||
background: rgba(35, 96, 140, 0.05);
|
||||
}
|
||||
</style>
|
||||
|
||||
.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>
|
||||
|
||||
Reference in New Issue
Block a user