feat: Complete frontend integration with AI news system

- Update IndustryNews page with AI summary indicators
- Enhance NewsDetail page with AI summary identification
- Completely remove mock data from newsService
- Add smart-elderly-care category support
- Include frontend API test utility

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
BrunoGao
2025-10-06 23:23:56 +08:00
co-authored by Claude
parent e64e6ae227
commit 40d5a361bc
5 changed files with 127 additions and 234 deletions
+12 -6
View File
@@ -28,10 +28,10 @@ const IndustryNews: React.FC = () => {
{ value: 'all', label: '全部动态', icon: '📰', color: 'from-blue-500 to-cyan-500' },
{ value: 'healthcare-policy', label: '康养政策', icon: '🏥', color: 'from-green-500 to-emerald-500' },
{ value: 'medical-robotics', label: '医疗机器人', icon: '🤖', color: 'from-purple-500 to-violet-500' },
{ value: 'industry-trends', label: '行业动态', icon: '📈', color: 'from-orange-500 to-amber-500' },
{ value: 'smart-elderly-care', label: '智慧养老', icon: '👴', color: 'from-indigo-500 to-blue-500' },
{ value: 'tech-innovation', label: '技术前沿', icon: '🚀', color: 'from-pink-500 to-rose-500' },
{ value: 'investment', label: '投融资', icon: '💰', color: 'from-yellow-500 to-orange-500' },
{ value: 'regulatory', label: '监管动态', icon: '⚖️', color: 'from-red-500 to-pink-500' },
{ value: 'industry-trends', label: '行业动态', icon: '📈', color: 'from-orange-500 to-amber-500' },
];
const sortOptions = [
@@ -228,10 +228,16 @@ const IndustryNews: React.FC = () => {
{news.title}
</h3>
{/* Summary */}
<p className="text-gray-400 mb-4 line-clamp-3 text-sm leading-relaxed">
{news.summary}
</p>
{/* AI Summary */}
<div className="mb-4">
<div className="flex items-center gap-2 mb-2">
<span className="w-1.5 h-1.5 bg-cyan-400 rounded-full animate-pulse"></span>
<span className="text-xs text-cyan-400 font-medium">AI摘要</span>
</div>
<p className="text-gray-400 line-clamp-3 text-sm leading-relaxed">
{news.summary}
</p>
</div>
{/* Meta Info */}
<div className="flex items-center justify-between text-sm text-gray-500 mb-4">
+17 -3
View File
@@ -189,6 +189,17 @@ const NewsDetail: React.FC = () => {
)}
</header>
{/* AI摘要标识 */}
<div className="mb-6 p-4 bg-gradient-to-r from-cyan-500/10 to-blue-500/10 border border-cyan-500/20 rounded-lg">
<div className="flex items-center gap-2 text-cyan-400 text-sm font-medium mb-2">
<span className="w-2 h-2 bg-cyan-400 rounded-full animate-pulse"></span>
AI智能摘要
</div>
<p className="text-gray-300 text-sm">
以下内容为AI基于原文生成的智能摘要,如需查看完整原文,请点击下方"查看原文"链接。
</p>
</div>
{/* 文章内容 */}
<div className="prose prose-invert prose-cyan max-w-none">
<ReactMarkdown
@@ -251,14 +262,17 @@ const NewsDetail: React.FC = () => {
</div>
{/* 原文链接 */}
<div className="mt-8 p-4 bg-gray-700 rounded-lg">
<div className="mt-8 p-6 bg-gradient-to-r from-gray-700 to-gray-800 rounded-xl border border-gray-600">
<div className="flex items-center justify-between">
<span className="text-gray-400">查看原文</span>
<div>
<h3 className="text-white font-semibold mb-1">查看完整原文</h3>
<p className="text-gray-400 text-sm">获取更多详细信息和完整内容</p>
</div>
<a
href={article.sourceUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 text-cyan-400 hover:text-cyan-300 transition-colors"
className="inline-flex items-center gap-2 px-4 py-2 bg-cyan-500 hover:bg-cyan-600 text-white rounded-lg transition-colors font-medium"
>
访问 {article.source}
<ExternalLink size={16} />
+44 -224
View File
@@ -1,25 +1,12 @@
// 新闻服务 - 支持多种数据源
// 新闻服务 - 只使用真实API数据
import { NewsItem, NewsCategory, NewsSearchParams, NewsResponse, NewsStats } from '../types/news';
// 外部API配置
// API配置
const NEWS_API_CONFIG = {
// 自建新闻API(推荐)
CUSTOM_API: {
baseUrl: import.meta.env.VITE_NEWS_API_URL || 'http://localhost:3001/api',
baseUrl: import.meta.env.VITE_NEWS_API_URL || 'http://localhost:3002/api',
apiKey: import.meta.env.VITE_NEWS_API_KEY || 'omniverse_news_2024',
},
// 第三方新闻API备选方案
NEWSAPI: {
baseUrl: 'https://newsapi.org/v2',
apiKey: import.meta.env.VITE_NEWSAPI_KEY || '',
},
// 聚合数据API
JUHE: {
baseUrl: 'http://v.juhe.cn/toutiao',
apiKey: import.meta.env.VITE_JUHE_KEY || '',
}
};
@@ -59,33 +46,20 @@ class NewsService {
if (cached) return cached;
try {
// 优先使用自建API
if (NEWS_API_CONFIG.CUSTOM_API.baseUrl) {
const result = await this.fetchFromCustomAPI(params);
this.setCachedData(cacheKey, result);
return result;
}
// 如果没有API配置,返回空结果
return {
items: [],
total: 0,
page: 1,
totalPages: 0,
hasNext: false,
hasPrev: false,
};
const result = await this.fetchFromCustomAPI(params);
this.setCachedData(cacheKey, result);
return result;
} catch (error) {
console.error('Failed to fetch news:', error);
// 返回空结果而不是mock数据
console.error('Failed to fetch news from API:', error);
// 返回空结果而不是模拟数据
return {
items: [],
total: 0,
page: 1,
totalPages: 0,
hasNext: false,
hasPrev: false,
hasPrev: false
};
}
}
@@ -103,7 +77,9 @@ class NewsService {
if (params.sortBy) queryParams.append('sortBy', params.sortBy);
if (params.sortOrder) queryParams.append('sortOrder', params.sortOrder);
const response = await fetch(`${baseUrl}/news?${queryParams}`, {
const url = `${baseUrl}/news?${queryParams}`;
const response = await fetch(url, {
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json',
@@ -134,42 +110,31 @@ class NewsService {
if (cached) return cached;
try {
if (NEWS_API_CONFIG.CUSTOM_API.baseUrl) {
const response = await fetch(`${NEWS_API_CONFIG.CUSTOM_API.baseUrl}/stats`, {
headers: {
'X-API-Key': NEWS_API_CONFIG.CUSTOM_API.apiKey,
},
});
if (response.ok) {
const result = await response.json();
this.setCachedData(cacheKey, result);
return result;
}
}
const response = await fetch(`${NEWS_API_CONFIG.CUSTOM_API.baseUrl}/stats`, {
headers: {
'X-API-Key': NEWS_API_CONFIG.CUSTOM_API.apiKey,
},
});
// 如果没有API,返回空统计
const result = {
totalNews: 0,
todayNews: 0,
hotNews: 0,
categoryCounts: {},
sourceCounts: {},
trending: [],
};
this.setCachedData(cacheKey, result);
return result;
if (response.ok) {
const result = await response.json();
this.setCachedData(cacheKey, result);
return result;
} else {
throw new Error(`Stats API failed: ${response.status}`);
}
} catch (error) {
console.error('Failed to fetch news stats:', error);
// 返回空统计而不是模拟数据
return {
totalNews: 0,
todayNews: 0,
hotNews: 0,
categoryCounts: {},
sourceCounts: {},
trending: [],
};
trending: []
} as NewsStats;
}
}
@@ -186,30 +151,27 @@ class NewsService {
if (cached) return cached;
try {
if (NEWS_API_CONFIG.CUSTOM_API.baseUrl) {
const response = await fetch(`${NEWS_API_CONFIG.CUSTOM_API.baseUrl}/news/hot?limit=${limit}`, {
headers: {
'X-API-Key': NEWS_API_CONFIG.CUSTOM_API.apiKey,
},
});
if (response.ok) {
const result = await response.json();
this.setCachedData(cacheKey, result);
return result;
}
}
const response = await fetch(`${NEWS_API_CONFIG.CUSTOM_API.baseUrl}/news/hot?limit=${limit}`, {
headers: {
'X-API-Key': NEWS_API_CONFIG.CUSTOM_API.apiKey,
},
});
// Fallback
const response = await this.getNews({ limit, sortBy: 'viewCount' });
const result = response.items.filter(item => item.isHot);
this.setCachedData(cacheKey, result);
return result;
if (response.ok) {
const result = await response.json();
this.setCachedData(cacheKey, result);
return result;
} else {
// 如果没有专门的hot接口,使用普通接口筛选
const newsResponse = await this.getNews({ limit: limit * 2, sortBy: 'viewCount' });
const result = newsResponse.items.filter(item => item.isHot).slice(0, limit);
this.setCachedData(cacheKey, result);
return result;
}
} catch (error) {
console.error('Failed to fetch hot news:', error);
const response = await this.getNews({ limit, sortBy: 'viewCount' });
return response.items.filter(item => item.isHot);
return [];
}
}
@@ -219,148 +181,6 @@ class NewsService {
return response.items;
}
// 模拟新闻数据(开发和fallback使用)
private async getMockNews(params: NewsSearchParams = {}): Promise<NewsResponse> {
const { page = 1, limit = 10, category, query } = params;
let mockNews = this.generateMockNews();
// 按分类筛选
if (category) {
mockNews = mockNews.filter(item => item.category === category);
}
// 按关键词搜索
if (query) {
const searchQuery = query.toLowerCase();
mockNews = mockNews.filter(item =>
item.title.toLowerCase().includes(searchQuery) ||
item.summary.toLowerCase().includes(searchQuery) ||
item.tags.some(tag => tag.toLowerCase().includes(searchQuery))
);
}
// 分页
const startIndex = (page - 1) * limit;
const endIndex = startIndex + limit;
const items = mockNews.slice(startIndex, endIndex);
return {
items,
total: mockNews.length,
page,
totalPages: Math.ceil(mockNews.length / limit),
hasNext: endIndex < mockNews.length,
hasPrev: page > 1,
};
}
// 模拟统计数据
private getMockStats(): NewsStats {
return {
totalNews: 1247,
todayNews: 23,
hotNews: 8,
categoryCounts: {
'healthcare-policy': 156,
'medical-robotics': 89,
'industry-trends': 234,
'tech-innovation': 178,
'investment': 145,
'regulatory': 67,
'market-analysis': 123,
'corporate-news': 89,
'research': 112,
'international': 54,
},
sourceCounts: {
'健康界': 89,
'医学界': 76,
'动脉网': 65,
'36氪': 54,
'亿欧': 43,
},
trending: [
{ keyword: 'AI医疗', count: 45, trend: 'up' },
{ keyword: '智能养老', count: 32, trend: 'up' },
{ keyword: '医疗机器人', count: 28, trend: 'stable' },
{ keyword: '数字疗法', count: 23, trend: 'up' },
{ keyword: '远程医疗', count: 19, trend: 'down' },
],
};
}
// 生成模拟新闻数据
private generateMockNews(): NewsItem[] {
const categories: NewsCategory[] = [
'healthcare-policy', 'medical-robotics', 'industry-trends',
'tech-innovation', 'investment', 'regulatory'
];
const sources = ['健康界', '医学界', '动脉网', '36氪', '亿欧', '投中网', '甲子光年'];
const mockTitles = [
'国家卫健委发布智慧医院建设指导意见',
'医疗机器人行业迎来政策利好,市场规模预计突破千亿',
'AI诊断技术获得NMPA批准,准确率达95%以上',
'康养产业数字化转型加速,智能设备普及率大幅提升',
'某知名医疗机器人公司完成B轮融资,估值达50亿',
'远程医疗服务覆盖率达85%,偏远地区医疗资源配置优化',
'智能可穿戴设备在慢病管理中的应用效果显著',
'医疗大数据安全管理新规正式实施',
'手术机器人技术突破,微创手术成功率提升30%',
'养老院智能化改造项目启动,覆盖全国重点城市',
'数字疗法首次纳入医保目录,患者负担大幅减轻',
'国际医疗AI标准化组织成立,中国企业积极参与',
'5G+医疗应用场景不断拓展,急救效率显著提升',
'医疗机器人出口量创新高,"中国制造"走向世界',
'智慧医院建设标准发布,推动行业规范化发展'
];
return Array.from({ length: 50 }, (_, index) => {
const category = categories[index % categories.length];
const source = sources[index % sources.length];
const title = mockTitles[index % mockTitles.length];
const publishedAt = new Date(Date.now() - Math.random() * 7 * 24 * 60 * 60 * 1000).toISOString();
return {
id: `news_${index + 1}`,
title,
summary: `${title}的详细内容摘要,包含关键信息和重要数据。本文深入分析了相关政策影响、技术发展趋势以及市场前景,为行业发展提供重要参考。`,
category,
tags: this.generateTags(category),
source,
sourceUrl: `https://${source.toLowerCase()}.com/article/${index + 1}`,
publishedAt,
crawledAt: new Date().toISOString(),
imageUrl: `https://images.unsplash.com/photo-${1576091160000 + index}?w=400&h=200&fit=crop`,
priority: ['high', 'medium', 'low'][index % 3] as any,
isHot: index < 8,
viewCount: Math.floor(Math.random() * 5000) + 100,
region: index % 3 === 0 ? '北京' : index % 3 === 1 ? '上海' : '深圳',
};
});
}
// 根据分类生成标签
private generateTags(category: NewsCategory): string[] {
const tagMap: Record<NewsCategory, string[]> = {
'healthcare-policy': ['政策', '卫健委', '医保', '监管'],
'medical-robotics': ['机器人', '手术', 'AI', '自动化'],
'industry-trends': ['趋势', '市场', '发展', '前景'],
'tech-innovation': ['创新', '技术', '突破', '研发'],
'investment': ['融资', '投资', '估值', '资本'],
'regulatory': ['法规', '标准', '合规', '监管'],
'market-analysis': ['分析', '报告', '数据', '市场'],
'corporate-news': ['企业', '公司', '合作', '战略'],
'research': ['研究', '论文', '成果', '学术'],
'international': ['国际', '全球', '海外', '出口'],
};
const baseTags = tagMap[category] || ['新闻'];
return baseTags.slice(0, Math.floor(Math.random() * 3) + 2);
}
// 清除缓存
clearCache(): void {
this.cache.clear();
+2 -1
View File
@@ -23,9 +23,10 @@ export interface NewsItem {
export type NewsCategory =
| 'healthcare-policy' // 康养政策
| 'medical-robotics' // 医疗机器人
| 'industry-trends' // 行业动态
| 'smart-elderly-care' // 智慧养老
| 'tech-innovation' // 技术前沿
| 'investment' // 投融资
| 'industry-trends' // 行业动态
| 'regulatory' // 监管动态
| 'market-analysis' // 市场分析
| 'corporate-news' // 企业动态
+52
View File
@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html>
<head>
<title>API连接测试</title>
</head>
<body>
<h1>前端API连接测试</h1>
<button onclick="testAPI()">测试API连接</button>
<div id="result"></div>
<script>
async function testAPI() {
const resultDiv = document.getElementById('result');
resultDiv.innerHTML = '正在测试...';
try {
console.log('开始测试API连接...');
const response = await fetch('http://localhost:3002/api/news?limit=3', {
headers: {
'X-API-Key': 'omniverse_news_2024',
'Content-Type': 'application/json',
},
});
console.log('API响应状态:', response.status);
if (!response.ok) {
throw new Error(`API请求失败: ${response.status}`);
}
const data = await response.json();
console.log('API数据:', data);
resultDiv.innerHTML = `
<h2>✅ API连接成功</h2>
<p>状态码: ${response.status}</p>
<p>返回新闻数量: ${data.data?.length || 0}</p>
<h3>第一条新闻:</h3>
<pre>${JSON.stringify(data.data?.[0] || {}, null, 2)}</pre>
`;
} catch (error) {
console.error('API测试失败:', error);
resultDiv.innerHTML = `
<h2>❌ API连接失败</h2>
<p>错误: ${error.message}</p>
`;
}
}
</script>
</body>
</html>