feat: 实现内容管理API及相关功能

refactor(services-section): 重构服务展示组件使用API数据
refactor(news-section): 重构新闻展示组件使用API数据
refactor(products-section): 重构产品展示组件使用API数据

test: 添加API客户端和服务钩子的单元测试
test(e2e): 添加配置验证和API响应格式的端到端测试

ci: 更新Playwright测试配置
This commit is contained in:
张翔
2026-03-13 18:55:25 +08:00
parent 72745456d2
commit ac2672729f
20 changed files with 3934 additions and 153 deletions
+131
View File
@@ -0,0 +1,131 @@
import { apiClient } from './client';
import { Product, NewsItem, Service, ContentItem } from './types';
class ContentService {
async getProducts(featuredIds?: string[]): Promise<Product[]> {
try {
const data = await apiClient.get<ContentItem[]>('/api/content', {
type: 'product',
status: 'published',
});
let products = data.map(item => this.transformToProduct(item));
if (featuredIds && featuredIds.length > 0) {
products = products.filter(p => featuredIds.includes(p.id));
}
return products;
} catch (error) {
console.error('Failed to fetch products:', error);
return [];
}
}
async getNews(
categories?: string[],
limit?: number,
sortOrder: 'asc' | 'desc' = 'desc'
): Promise<NewsItem[]> {
try {
const data = await apiClient.get<ContentItem[]>('/api/content', {
type: 'news',
status: 'published',
});
let news = data.map(item => this.transformToNews(item));
if (categories && categories.length > 0) {
news = news.filter(n => categories.includes(n.category));
}
if (sortOrder === 'desc') {
news.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
} else {
news.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
}
if (limit && limit > 0) {
news = news.slice(0, limit);
}
return news;
} catch (error) {
console.error('Failed to fetch news:', error);
return [];
}
}
async getServices(ids?: string[]): Promise<Service[]> {
try {
const data = await apiClient.get<ContentItem[]>('/api/content', {
type: 'service',
status: 'published',
});
let services = data.map(item => this.transformToService(item));
if (ids && ids.length > 0) {
services = services.filter(s => ids.includes(s.id));
}
return services;
} catch (error) {
console.error('Failed to fetch services:', error);
return [];
}
}
private transformToProduct(item: ContentItem): Product {
const metadata = item.metadata || {};
return {
id: item.id,
title: item.title,
description: item.excerpt || '',
category: item.category || '',
features: metadata.features || [],
benefits: metadata.benefits || [],
pricing: metadata.pricing,
image: item.coverImage,
slug: item.slug,
};
}
private transformToNews(item: ContentItem): NewsItem {
return {
id: item.id,
title: item.title,
excerpt: item.excerpt || '',
content: item.content,
date: item.publishedAt ? this.formatDate(item.publishedAt) : this.formatDate(item.createdAt),
category: item.category || '公司新闻',
image: item.coverImage,
slug: item.slug,
};
}
private transformToService(item: ContentItem): Service {
const metadata = item.metadata || {};
return {
id: item.id,
title: item.title,
description: item.excerpt || '',
icon: metadata.icon || 'Code',
features: metadata.features || [],
benefits: metadata.benefits || [],
slug: item.slug,
};
}
private formatDate(dateString: string): string {
try {
const date = new Date(dateString);
const isoString = date.toISOString();
return isoString.split('T')[0] || dateString;
} catch {
return dateString;
}
}
}
export const contentService = new ContentService();