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:
@@ -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">
|
||||
|
||||
@@ -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
@@ -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
@@ -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' // 企业动态
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user