feat: Add independent pages for blog, articles, and docs

## 新增独立页面系统

### 🏠 独立页面
- **BlogList** (`/blog`) - 完整的博客浏览体验
- **ArticleList** (`/articles`) - 专门的技术文章展示
- **DocsHome** (`/docs`) - 全新的开发者文档主页

### ✨ 核心功能

#### BlogList 页面特色
- 🔍 智能搜索和实时筛选
- 🏷️ 分类过滤器(AI健康监护、智能穿戴等)
- 📊 多种排序选项(最新、热门、受欢迎)
- 🎛️ 网格/列表视图切换
- 🔗 URL参数同步(搜索、分类状态)

#### ArticleList 页面特色
- 📚 6种文章类型分类(教程指南、研究报告、案例分析等)
- 🎨 紫色主题差异化设计
- 📋 更详细的文章标签和元信息显示
- 🔢 高级排序(包含字母排序)
- ⭐ 精选文章标识系统

#### DocsHome 页面特色
- 📖 5大文档分类(快速开始、API参考、集成指南等)
- ⭐ 精选文档推荐系统
- 🚀 快速操作按钮(下载SDK、在线演示、技术支持)
- 📊 最近更新动态展示
- 🔍 实时搜索过滤功能

### 🧭 导航系统升级

#### 智能导航路由
- **博客菜单**: 技术博客 → `/blog` | 技术文章 → `/articles` | 首页博客 → `/#blog`
- **文档菜单**: 开发者文档 → `/docs` | API参考 → `/docs` | 首页文档 → `/#docs`
- **路由识别**: 自动区分独立页面链接和锚点链接

#### 移动端适配
- 🔧 桌面端和移动端导航完全支持新页面
- 📱 保持首页滚动导航功能完整性

### 🎯 SectionTags 系统扩展

#### 项目管理标签(16种新增)
- 📍 项目背景、🔄 实施过程、📋 需求分析
- 🧠 方案设计、⚡ 技术实现、✅ 测试验证
- 🚀 部署上线、📊 效果评估、💡 经验总结
- 🛡️ 风险管理、💼 资源配置、💬 沟通协调
- 🏆 质量保证、⚡ 性能优化、👥 用户培训、❤️ 运维支持

#### 智能标签识别
- 🎯 项目管理标签享有最高识别优先级
- 🤖 支持36种专业标签的自动匹配
- 📝 创建详细的使用指南文档

### 🔗 首页集成优化
- **BlogSection**: 双按钮设计(查看所有博客 + 浏览技术文章)
- **DocsSection**: 新增"浏览完整文档库"按钮
- **保持兼容**: 首页sections功能完全保持

### 🎨 用户体验提升
- **零破坏性更改** - 原有功能完全保持
- **SEO友好** - URL参数支持分享特定搜索/分类
- **性能优化** - 组件重用和智能渲染
- **响应式设计** - 完整的移动端适配

现在用户可以通过导航直接访问独立的博客、文章和文档页面,享受专业的浏览和搜索体验!
This commit is contained in:
BrunoGao
2025-10-01 21:37:59 +08:00
parent 6720b92751
commit 5d5975be4f
10 changed files with 1914 additions and 58 deletions
+131
View File
@@ -0,0 +1,131 @@
# Section Tags 使用指南
## 概述
SectionTags 系统现已扩展为包含 **36种专业标签**,特别针对项目管理和技术实施场景进行了优化。
## 🎯 项目管理相关标签 (16种)
### 项目生命周期标签
| 标签类型 | 显示名称 | 适用章节标题示例 | 颜色主题 |
|---------|---------|-----------------|----------|
| `project-background` | 项目背景 | "项目背景"、"项目启动背景" | Indigo |
| `requirements-analysis` | 需求分析 | "需求分析"、"业务需求梳理"、"需求调研" | Emerald |
| `solution-design` | 方案设计 | "方案设计"、"解决方案规划" | Purple |
| `technical-implementation` | 技术实现 | "技术实现"、"技术开发"、"代码实现" | Gray |
| `testing-validation` | 测试验证 | "测试验证"、"系统测试"、"验证阶段" | Green |
| `deployment-launch` | 部署上线 | "部署上线"、"系统发布"、"正式上线" | Orange |
| `results-evaluation` | 效果评估 | "效果评估"、"项目评价"、"成果评估" | Cyan |
### 项目管理标签
| 标签类型 | 显示名称 | 适用章节标题示例 | 颜色主题 |
|---------|---------|-----------------|----------|
| `implementation-process` | 实施过程 | "实施过程"、"实施流程"、"执行步骤" | Blue |
| `risk-management` | 风险管理 | "风险管理"、"风险控制"、"风险防范" | Red |
| `resource-allocation` | 资源配置 | "资源配置"、"人员分配"、"资源调配" | Teal |
| `stakeholder-communication` | 沟通协调 | "沟通协调"、"团队配合"、"干系人沟通" | Pink |
| `quality-assurance` | 质量保证 | "质量保证"、"质量管控"、"QA流程" | Amber |
| `performance-optimization` | 性能优化 | "性能优化"、"系统优化"、"性能提升" | Violet |
| `user-training` | 用户培训 | "用户培训"、"使用指导"、"培训计划" | Sky |
| `maintenance-support` | 运维支持 | "运维支持"、"系统维护"、"技术支持" | Rose |
| `lessons-learned` | 经验总结 | "经验总结"、"项目教训"、"心得体会" | Yellow |
## 🔬 专业内容标签 (20种)
### 文章结构标签
- `introduction` - 引言
- `conclusion` - 结论
- `summary` - 总结
### 专家与案例标签
- `expert-opinion` - 专家评价
- `expert-interview` - 专家访谈
- `case-study` - 案例研究
- `best-practices` - 最佳实践
### 技术标签
- `technical-details` - 技术细节
- `architecture` - 系统架构
### 分析标签
- `analysis` - 深度分析
- `insights` - 洞察观点
- `trends` - 发展趋势
### 前瞻标签
- `future-outlook` - 未来展望
- `roadmap` - 发展路线
### 其他标签
- `methodology` - 方法论
- `timeline` - 时间线
- `important` - 重点关注
- `warning` - 注意事项
## 🤖 智能识别规则
系统会根据章节标题自动匹配最适合的标签:
### 项目管理优先级规则
1. **项目背景**: "项目背景"、"项目启动背景"
2. **实施过程**: "实施过程"、"实施流程"、"执行步骤"
3. **需求分析**: "需求分析"、"业务需求梳理"
4. **方案设计**: "方案设计"、"解决方案规划"
5. **技术实现**: "技术实现"、"技术开发"
6. **测试验证**: "测试验证"、"系统测试"
7. **部署上线**: "部署上线"、"系统发布"
8. **效果评估**: "效果评估"、"项目评价"
### 示例匹配
```markdown
# 项目背景 → 📍 项目背景标签
## 需求分析 → 📋 需求分析标签
### 技术实现 → ⚡ 技术实现标签
#### 测试验证 → ✅ 测试验证标签
##### 部署上线 → 🚀 部署上线标签
###### 效果评估 → 📊 效果评估标签
```
## 💡 使用建议
### 1. 项目文档结构建议
```markdown
# 项目背景
## 需求分析
### 方案设计
#### 技术实现
##### 测试验证
###### 部署上线
####### 效果评估
######## 经验总结
```
### 2. 技术博客结构建议
```markdown
# 引言
## 技术细节
### 系统架构
#### 最佳实践
##### 案例研究
###### 专家评价
####### 未来展望
######## 结论
```
## 🎨 视觉效果
每个标签都有独特的:
- **颜色主题**: 基于语义的颜色编码
- **图标**: 直观的视觉标识
- **动画效果**: 平滑的入场动画
- **响应式设计**: 适配不同屏幕尺寸
## 🔧 自定义扩展
开发者可以轻松添加新的标签类型:
1. 在 `sectionTags` 对象中添加新标签定义
2. 在 `getSectionTagType` 函数中添加匹配规则
3. 系统将自动应用新标签
这个系统为技术博客和项目文档提供了专业的视觉层次和语义标识。
+6
View File
@@ -2,6 +2,9 @@ import { Routes, Route } from 'react-router-dom'
import Navigation from './components/Navigation'
import HomePage from './pages/HomePage'
import BlogDetail from './pages/BlogDetail'
import BlogList from './pages/BlogList'
import ArticleList from './pages/ArticleList'
import DocsHome from './pages/DocsHome'
import ProductDetail from './pages/ProductDetail'
import SolutionDetail from './pages/SolutionDetail'
import Footer from './components/sections/Footer'
@@ -12,8 +15,11 @@ function App() {
<Navigation />
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/blog" element={<BlogList />} />
<Route path="/blog/:id" element={<BlogDetail />} />
<Route path="/articles" element={<ArticleList />} />
<Route path="/article/:id" element={<BlogDetail />} />
<Route path="/docs" element={<DocsHome />} />
<Route path="/product/:productId" element={<ProductDetail />} />
<Route path="/solution/:solutionId" element={<SolutionDetail />} />
</Routes>
+80 -39
View File
@@ -77,19 +77,25 @@ const Navigation: React.FC = () => {
{
label: t('nav.blog'),
id: 'blog',
href: '/website/#blog',
icon: <Newspaper size={16} />
href: '/blog',
icon: <Newspaper size={16} />,
dropdown: [
{ label: '技术博客', href: '/blog', icon: <Newspaper size={14} /> },
{ label: '技术文章', href: '/articles', icon: <FileText size={14} /> },
{ label: '首页博客', href: '/website/#blog', icon: <Home size={14} /> },
]
},
{
label: t('nav.docs'),
id: 'docs',
href: '/website/#docs',
href: '/docs',
icon: <Book size={16} />,
dropdown: [
{ label: t('nav.dropdown.gettingStarted'), href: '/website/#docs', icon: <FileText size={14} /> },
{ label: t('nav.dropdown.apiDocs'), href: '/website/#docs', icon: <Code size={14} /> },
{ label: t('nav.dropdown.tutorials'), href: '/website/#docs', icon: <GraduationCap size={14} /> },
{ label: t('nav.dropdown.support'), href: '/website/#docs', icon: <MessageCircle size={14} /> },
{ label: '开发者文档', href: '/docs', icon: <FileText size={14} /> },
{ label: 'API 参考', href: '/docs', icon: <Code size={14} /> },
{ label: '教程指南', href: '/docs', icon: <GraduationCap size={14} /> },
{ label: '技术支持', href: '/docs', icon: <MessageCircle size={14} /> },
{ label: '首页文档', href: '/website/#docs', icon: <Home size={14} /> },
]
},
{
@@ -167,27 +173,50 @@ const Navigation: React.FC = () => {
onMouseEnter={() => item.dropdown && setActiveDropdown(item.id)}
onMouseLeave={() => setActiveDropdown(null)}
>
<motion.a
href={item.href}
onClick={(e) => handleSmoothScroll(e, item.id)}
whileHover={{ y: -2 }}
className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all duration-300 ${
activeDropdown === item.id
? 'text-cyan-400 bg-cyan-500/10'
: 'text-gray-300 hover:text-cyan-400 hover:bg-gray-800/50'
}`}
>
{item.icon}
<span>{item.label}</span>
{item.dropdown && (
<ChevronDown
size={14}
className={`transition-transform duration-300 ${
activeDropdown === item.id ? 'rotate-180' : ''
}`}
/>
)}
</motion.a>
{item.href.startsWith('/blog') || item.href.startsWith('/articles') || item.href.startsWith('/docs') ? (
<Link
to={item.href}
className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all duration-300 ${
activeDropdown === item.id
? 'text-cyan-400 bg-cyan-500/10'
: 'text-gray-300 hover:text-cyan-400 hover:bg-gray-800/50'
}`}
onClick={() => setActiveDropdown(null)}
>
{item.icon}
<span>{item.label}</span>
{item.dropdown && (
<ChevronDown
size={14}
className={`transition-transform duration-300 ${
activeDropdown === item.id ? 'rotate-180' : ''
}`}
/>
)}
</Link>
) : (
<motion.a
href={item.href}
onClick={(e) => handleSmoothScroll(e, item.id)}
whileHover={{ y: -2 }}
className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all duration-300 ${
activeDropdown === item.id
? 'text-cyan-400 bg-cyan-500/10'
: 'text-gray-300 hover:text-cyan-400 hover:bg-gray-800/50'
}`}
>
{item.icon}
<span>{item.label}</span>
{item.dropdown && (
<ChevronDown
size={14}
className={`transition-transform duration-300 ${
activeDropdown === item.id ? 'rotate-180' : ''
}`}
/>
)}
</motion.a>
)}
{/* Dropdown Menu */}
<AnimatePresence>
@@ -200,7 +229,7 @@ const Navigation: React.FC = () => {
className="absolute top-full left-0 mt-2 w-64 bg-gray-900/95 backdrop-blur-xl rounded-xl shadow-2xl border border-gray-800 overflow-hidden"
>
{item.dropdown.map((subItem, index) => {
const isRouterLink = subItem.href.startsWith('/product/') || subItem.href.startsWith('/solution/');
const isRouterLink = subItem.href.startsWith('/product/') || subItem.href.startsWith('/solution/') || subItem.href.startsWith('/blog') || subItem.href.startsWith('/articles') || subItem.href.startsWith('/docs');
if (isRouterLink) {
return (
@@ -360,19 +389,31 @@ const Navigation: React.FC = () => {
<div className="space-y-2 mb-6">
{navigationItems.map((item) => (
<div key={item.id}>
<a
href={item.href}
onClick={(e) => handleSmoothScroll(e, item.id)}
className="flex items-center gap-3 px-4 py-3 text-gray-300 hover:text-cyan-400 hover:bg-gray-800/50 rounded-lg transition-all duration-300"
>
{item.icon}
<span>{item.label}</span>
{item.dropdown && <ChevronDown size={14} className="ml-auto" />}
</a>
{item.href.startsWith('/blog') || item.href.startsWith('/articles') || item.href.startsWith('/docs') ? (
<Link
to={item.href}
className="flex items-center gap-3 px-4 py-3 text-gray-300 hover:text-cyan-400 hover:bg-gray-800/50 rounded-lg transition-all duration-300"
onClick={() => setIsMobileMenuOpen(false)}
>
{item.icon}
<span>{item.label}</span>
{item.dropdown && <ChevronDown size={14} className="ml-auto" />}
</Link>
) : (
<a
href={item.href}
onClick={(e) => handleSmoothScroll(e, item.id)}
className="flex items-center gap-3 px-4 py-3 text-gray-300 hover:text-cyan-400 hover:bg-gray-800/50 rounded-lg transition-all duration-300"
>
{item.icon}
<span>{item.label}</span>
{item.dropdown && <ChevronDown size={14} className="ml-auto" />}
</a>
)}
{item.dropdown && (
<div className="ml-8 mt-2 space-y-1">
{item.dropdown.map((subItem, index) => {
const isRouterLink = subItem.href.startsWith('/product/') || subItem.href.startsWith('/solution/');
const isRouterLink = subItem.href.startsWith('/product/') || subItem.href.startsWith('/solution/') || subItem.href.startsWith('/blog') || subItem.href.startsWith('/articles') || subItem.href.startsWith('/docs');
if (isRouterLink) {
return (
+198 -4
View File
@@ -250,6 +250,200 @@ const sectionTags: Record<string, SectionTag> = {
}
},
// 项目管理相关
'project-background': {
type: 'project-background',
label: '项目背景',
description: '项目启动背景介绍',
icon: <Flag size={16} />,
color: {
bg: 'bg-indigo-500/10',
border: 'border-indigo-500/30',
text: 'text-indigo-400',
icon: 'text-indigo-400'
}
},
'implementation-process': {
type: 'implementation-process',
label: '实施过程',
description: '项目实施步骤',
icon: <Settings size={16} />,
color: {
bg: 'bg-blue-500/10',
border: 'border-blue-500/30',
text: 'text-blue-400',
icon: 'text-blue-400'
}
},
'requirements-analysis': {
type: 'requirements-analysis',
label: '需求分析',
description: '业务需求梳理',
icon: <FileText size={16} />,
color: {
bg: 'bg-emerald-500/10',
border: 'border-emerald-500/30',
text: 'text-emerald-400',
icon: 'text-emerald-400'
}
},
'solution-design': {
type: 'solution-design',
label: '方案设计',
description: '解决方案设计',
icon: <Brain size={16} />,
color: {
bg: 'bg-purple-500/10',
border: 'border-purple-500/30',
text: 'text-purple-400',
icon: 'text-purple-400'
}
},
'technical-implementation': {
type: 'technical-implementation',
label: '技术实现',
description: '技术具体实现',
icon: <Code size={16} />,
color: {
bg: 'bg-gray-500/10',
border: 'border-gray-500/30',
text: 'text-gray-400',
icon: 'text-gray-400'
}
},
'testing-validation': {
type: 'testing-validation',
label: '测试验证',
description: '测试与验证阶段',
icon: <CheckCircle size={16} />,
color: {
bg: 'bg-green-500/10',
border: 'border-green-500/30',
text: 'text-green-400',
icon: 'text-green-400'
}
},
'deployment-launch': {
type: 'deployment-launch',
label: '部署上线',
description: '系统部署上线',
icon: <Rocket size={16} />,
color: {
bg: 'bg-orange-500/10',
border: 'border-orange-500/30',
text: 'text-orange-400',
icon: 'text-orange-400'
}
},
'results-evaluation': {
type: 'results-evaluation',
label: '效果评估',
description: '项目效果评估',
icon: <TrendingUp size={16} />,
color: {
bg: 'bg-cyan-500/10',
border: 'border-cyan-500/30',
text: 'text-cyan-400',
icon: 'text-cyan-400'
}
},
'lessons-learned': {
type: 'lessons-learned',
label: '经验总结',
description: '项目经验教训',
icon: <Lightbulb size={16} />,
color: {
bg: 'bg-yellow-500/10',
border: 'border-yellow-500/30',
text: 'text-yellow-400',
icon: 'text-yellow-400'
}
},
'risk-management': {
type: 'risk-management',
label: '风险管理',
description: '项目风险控制',
icon: <Shield size={16} />,
color: {
bg: 'bg-red-500/10',
border: 'border-red-500/30',
text: 'text-red-400',
icon: 'text-red-400'
}
},
'resource-allocation': {
type: 'resource-allocation',
label: '资源配置',
description: '项目资源分配',
icon: <Briefcase size={16} />,
color: {
bg: 'bg-teal-500/10',
border: 'border-teal-500/30',
text: 'text-teal-400',
icon: 'text-teal-400'
}
},
'stakeholder-communication': {
type: 'stakeholder-communication',
label: '沟通协调',
description: '干系人沟通',
icon: <MessageSquare size={16} />,
color: {
bg: 'bg-pink-500/10',
border: 'border-pink-500/30',
text: 'text-pink-400',
icon: 'text-pink-400'
}
},
'quality-assurance': {
type: 'quality-assurance',
label: '质量保证',
description: '项目质量管控',
icon: <Award size={16} />,
color: {
bg: 'bg-amber-500/10',
border: 'border-amber-500/30',
text: 'text-amber-400',
icon: 'text-amber-400'
}
},
'performance-optimization': {
type: 'performance-optimization',
label: '性能优化',
description: '系统性能提升',
icon: <Zap size={16} />,
color: {
bg: 'bg-violet-500/10',
border: 'border-violet-500/30',
text: 'text-violet-400',
icon: 'text-violet-400'
}
},
'user-training': {
type: 'user-training',
label: '用户培训',
description: '用户使用培训',
icon: <Users size={16} />,
color: {
bg: 'bg-sky-500/10',
border: 'border-sky-500/30',
text: 'text-sky-400',
icon: 'text-sky-400'
}
},
'maintenance-support': {
type: 'maintenance-support',
label: '运维支持',
description: '系统运维保障',
icon: <Heart size={16} />,
color: {
bg: 'bg-rose-500/10',
border: 'border-rose-500/30',
text: 'text-rose-400',
icon: 'text-rose-400'
}
},
// 其他特殊类型
'methodology': {
type: 'methodology',
@@ -269,10 +463,10 @@ const sectionTags: Record<string, SectionTag> = {
description: '发展时间轴',
icon: <Calendar size={16} />,
color: {
bg: 'bg-rose-500/10',
border: 'border-rose-500/30',
text: 'text-rose-400',
icon: 'text-rose-400'
bg: 'bg-slate-500/10',
border: 'border-slate-500/30',
text: 'text-slate-400',
icon: 'text-slate-400'
}
}
};
+21 -8
View File
@@ -181,20 +181,33 @@ const BlogSection: React.FC = () => {
))}
</div>
{/* Load More Button */}
{/* View More Buttons */}
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ duration: 0.8 }}
className="text-center"
>
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="px-8 py-4 bg-gradient-to-r from-cyan-500 to-green-500 text-black font-bold rounded-full hover:shadow-lg hover:shadow-cyan-500/30 transition-all duration-300"
>
{t('blog.loadMore')}
</motion.button>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Link to="/blog">
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="px-8 py-4 bg-gradient-to-r from-cyan-500 to-green-500 text-black font-bold rounded-full hover:shadow-lg hover:shadow-cyan-500/30 transition-all duration-300"
>
查看所有博客
</motion.button>
</Link>
<Link to="/articles">
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="px-8 py-4 border-2 border-cyan-500 text-cyan-400 font-bold rounded-full hover:bg-cyan-500/10 transition-all duration-300"
>
浏览技术文章
</motion.button>
</Link>
</div>
</motion.div>
</div>
</section>
+23 -5
View File
@@ -178,13 +178,13 @@ const DocsSection: React.FC = () => {
<p className="text-gray-400 text-sm mb-4">
{doc.description}
</p>
<a
href={doc.link}
<Link
to="/docs"
className="inline-flex items-center gap-2 text-cyan-400 hover:text-cyan-300 transition-colors"
>
{t('docs.learnMore')}
<ArrowRight size={16} />
</a>
</Link>
</div>
</motion.div>
))}
@@ -341,12 +341,30 @@ const DocsSection: React.FC = () => {
)}
</AnimatePresence>
{/* Help Section */}
{/* Browse All Docs Button */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.5 }}
className="mt-12 p-8 bg-gradient-to-r from-cyan-900/20 to-green-900/20 backdrop-blur-sm rounded-2xl border border-cyan-500/30"
className="text-center mt-12 mb-8"
>
<Link to="/docs">
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="px-8 py-4 bg-gradient-to-r from-cyan-500 to-green-500 text-black font-bold rounded-full hover:shadow-lg hover:shadow-cyan-500/30 transition-all duration-300"
>
浏览完整文档库
</motion.button>
</Link>
</motion.div>
{/* Help Section */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.6 }}
className="mt-8 p-8 bg-gradient-to-r from-cyan-900/20 to-green-900/20 backdrop-blur-sm rounded-2xl border border-cyan-500/30"
>
<div className="flex items-center justify-between">
<div>
+475
View File
@@ -0,0 +1,475 @@
import React, { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import { useTranslation } from 'react-i18next';
import { Link, useSearchParams } from 'react-router-dom';
import {
Calendar, Clock, ArrowRight, Tag, TrendingUp, User, Heart,
MessageCircle, Bookmark, Search, Filter, Grid, List as ListIcon,
ChevronDown, ArrowLeft, FileText, BookOpen, Lightbulb, Star
} from 'lucide-react';
import { blogPosts, BlogPost } from '../data/blogData';
const ArticleList: React.FC = () => {
const { t } = useTranslation();
const [searchParams, setSearchParams] = useSearchParams();
const [searchTerm, setSearchTerm] = useState('');
const [selectedCategory, setSelectedCategory] = useState('All Articles');
const [selectedType, setSelectedType] = useState('all');
const [sortBy, setSortBy] = useState('latest');
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
const [filteredPosts, setFilteredPosts] = useState<BlogPost[]>(blogPosts);
// Article types - different from blog categories
const articleTypes = [
{ value: 'all', label: '全部类型', icon: '📚', color: 'from-blue-500 to-cyan-500' },
{ value: 'tutorial', label: '教程指南', icon: '📖', color: 'from-green-500 to-emerald-500' },
{ value: 'research', label: '研究报告', icon: '🔬', color: 'from-purple-500 to-violet-500' },
{ value: 'case-study', label: '案例分析', icon: '📊', color: 'from-orange-500 to-amber-500' },
{ value: 'whitepaper', label: '白皮书', icon: '📄', color: 'from-gray-500 to-slate-500' },
{ value: 'industry', label: '行业洞察', icon: '💡', color: 'from-pink-500 to-rose-500' }
];
const categories = [
{ name: 'All Articles', count: blogPosts.length, icon: '📑' },
{ name: 'AI健康监护', count: blogPosts.filter(p => p.category === 'AI健康监护').length, icon: '🤖' },
{ name: '智能穿戴', count: blogPosts.filter(p => p.category === '智能穿戴').length, icon: '⌚' },
{ name: '护理机器人', count: blogPosts.filter(p => p.category === '护理机器人').length, icon: '🦾' },
{ name: '数据平台', count: blogPosts.filter(p => p.category === '数据平台').length, icon: '📊' }
];
const sortOptions = [
{ value: 'latest', label: '最新发布' },
{ value: 'oldest', label: '最早发布' },
{ value: 'popular', label: '最受欢迎' },
{ value: 'trending', label: '热门推荐' },
{ value: 'alphabetical', label: '按标题排序' }
];
useEffect(() => {
const category = searchParams.get('category');
const search = searchParams.get('search');
const type = searchParams.get('type');
if (category) setSelectedCategory(category);
if (search) setSearchTerm(search);
if (type) setSelectedType(type);
}, [searchParams]);
useEffect(() => {
let filtered = [...blogPosts];
// Filter by category
if (selectedCategory !== 'All Articles') {
filtered = filtered.filter(post => post.category === selectedCategory);
}
// Filter by article type (mock implementation based on tags)
if (selectedType !== 'all') {
switch (selectedType) {
case 'tutorial':
filtered = filtered.filter(post =>
post.tags.some(tag => tag.includes('教程') || tag.includes('指南') || tag.includes('入门'))
);
break;
case 'research':
filtered = filtered.filter(post =>
post.tags.some(tag => tag.includes('研究') || tag.includes('报告') || tag.includes('分析'))
);
break;
case 'case-study':
filtered = filtered.filter(post =>
post.tags.some(tag => tag.includes('案例') || tag.includes('实践') || tag.includes('应用'))
);
break;
case 'whitepaper':
filtered = filtered.filter(post =>
post.tags.some(tag => tag.includes('白皮书') || tag.includes('技术') || tag.includes('深度'))
);
break;
case 'industry':
filtered = filtered.filter(post =>
post.tags.some(tag => tag.includes('行业') || tag.includes('趋势') || tag.includes('洞察'))
);
break;
}
}
// Filter by search term
if (searchTerm) {
filtered = filtered.filter(post =>
post.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
post.excerpt.toLowerCase().includes(searchTerm.toLowerCase()) ||
post.tags.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase()))
);
}
// Sort posts
switch (sortBy) {
case 'latest':
filtered.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
break;
case 'oldest':
filtered.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
break;
case 'popular':
filtered.sort((a, b) => (b.likes + b.comments) - (a.likes + a.comments));
break;
case 'trending':
filtered = filtered.filter(post => post.trending).concat(
filtered.filter(post => !post.trending)
);
break;
case 'alphabetical':
filtered.sort((a, b) => a.title.localeCompare(b.title));
break;
}
setFilteredPosts(filtered);
}, [selectedCategory, selectedType, searchTerm, sortBy]);
const handleCategoryChange = (category: string) => {
setSelectedCategory(category);
updateURLParams({ category: category === 'All Articles' ? null : category });
};
const handleTypeChange = (type: string) => {
setSelectedType(type);
updateURLParams({ type: type === 'all' ? null : type });
};
const handleSearchChange = (term: string) => {
setSearchTerm(term);
updateURLParams({ search: term || null });
};
const updateURLParams = (updates: Record<string, string | null>) => {
const newParams = new URLSearchParams(searchParams);
Object.entries(updates).forEach(([key, value]) => {
if (value) {
newParams.set(key, value);
} else {
newParams.delete(key);
}
});
setSearchParams(newParams);
};
const ArticleCard: React.FC<{ post: BlogPost; index: number }> = ({ post, index }) => (
<motion.article
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: index * 0.1 }}
whileHover={{ y: -5 }}
className={`group relative bg-gray-900/50 backdrop-blur-sm rounded-2xl overflow-hidden border border-gray-800 hover:border-cyan-500/50 transition-all duration-500 ${
viewMode === 'list' ? 'flex' : ''
}`}
>
{/* Featured Badge */}
{post.trending && (
<div className="absolute top-4 left-4 z-10 bg-gradient-to-r from-yellow-500 to-orange-500 text-white px-3 py-1 rounded-full text-xs font-semibold flex items-center gap-1">
<Star size={12} />
精选文章
</div>
)}
{/* Image Container */}
<div className={`relative overflow-hidden ${viewMode === 'list' ? 'w-80 h-48' : 'h-64'}`}>
<img
src={post.image}
alt={post.title}
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-700"
/>
<div className="absolute inset-0 bg-gradient-to-t from-gray-900 via-gray-900/50 to-transparent opacity-80"></div>
{/* Article Type Badge */}
<div className="absolute top-4 right-4 bg-purple-500/20 backdrop-blur-md text-purple-400 px-4 py-2 rounded-full text-sm font-semibold border border-purple-500/30 flex items-center gap-2">
<FileText size={14} />
文章
</div>
{/* Reading Time */}
<div className="absolute bottom-4 left-4 bg-gray-900/80 backdrop-blur-sm px-3 py-2 rounded-full text-gray-300 text-sm flex items-center gap-2">
<BookOpen size={14} />
{post.readTime}
</div>
{/* Bookmark Icon */}
<motion.button
whileHover={{ scale: 1.2 }}
whileTap={{ scale: 0.9 }}
className="absolute bottom-4 right-4 bg-gray-900/80 backdrop-blur-sm p-2 rounded-full text-gray-400 hover:text-cyan-400 transition-colors"
>
<Bookmark size={20} />
</motion.button>
</div>
{/* Content */}
<div className="p-6 flex-1">
{/* Article Category */}
<div className="flex items-center gap-2 mb-3">
<span className="text-xs px-3 py-1 bg-cyan-500/20 text-cyan-400 rounded-full font-semibold">
{post.category}
</span>
<span className="text-xs text-gray-500">•</span>
<span className="text-xs text-gray-500">{post.date}</span>
</div>
{/* Title */}
<h3 className="text-xl font-bold mb-3 text-white group-hover:text-cyan-400 transition-colors duration-300 line-clamp-2">
{post.title}
</h3>
{/* Excerpt */}
<p className="text-gray-400 mb-4 line-clamp-3 text-sm leading-relaxed">
{post.excerpt}
</p>
{/* Tags */}
<div className="flex flex-wrap gap-2 mb-4">
{post.tags.slice(0, 4).map(tag => (
<span key={tag} className="text-xs px-2 py-1 bg-gray-800/50 text-gray-400 rounded border border-gray-700 flex items-center gap-1">
<Tag size={8} />
{tag}
</span>
))}
{post.tags.length > 4 && (
<span className="text-xs px-2 py-1 bg-gray-800/50 text-gray-500 rounded border border-gray-700">
+{post.tags.length - 4}
</span>
)}
</div>
{/* Author and Stats */}
<div className="flex items-center justify-between pt-4 border-t border-gray-800">
<div className="flex items-center gap-3">
<div className="flex items-center gap-2 text-sm text-gray-500">
<User size={14} />
{post.author}
</div>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-3">
<button className="flex items-center gap-1 text-gray-400 hover:text-red-400 transition-colors text-sm">
<Heart size={16} />
<span>{post.likes}</span>
</button>
<button className="flex items-center gap-1 text-gray-400 hover:text-cyan-400 transition-colors text-sm">
<MessageCircle size={16} />
<span>{post.comments}</span>
</button>
</div>
{/* Read More Button */}
<Link
to={`/blog/${post.id}`}
className="flex items-center gap-2 text-cyan-400 font-semibold hover:text-cyan-300 transition-colors text-sm"
>
<motion.span
whileHover={{ x: 3 }}
className="flex items-center gap-1"
>
阅读
<ArrowRight size={14} />
</motion.span>
</Link>
</div>
</div>
</div>
{/* Hover Glow Effect */}
<div className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-500 pointer-events-none">
<div className="absolute inset-0 bg-gradient-to-r from-purple-500/5 to-cyan-500/5"></div>
</div>
</motion.article>
);
return (
<div className="min-h-screen bg-gradient-to-b from-gray-900 via-black to-gray-900 pt-24">
{/* Header */}
<div className="container mx-auto px-4 py-12">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="text-center mb-12"
>
<h1 className="text-5xl font-bold mb-6 bg-gradient-to-r from-purple-400 to-cyan-400 bg-clip-text text-transparent">
技术文章
</h1>
<p className="text-xl text-gray-400 max-w-3xl mx-auto">
深度技术文章、研究报告与行业洞察,助您掌握AI健康科技发展趋势
</p>
</motion.div>
{/* Article Type Filter */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
className="flex flex-wrap justify-center gap-4 mb-8"
>
{articleTypes.map((type) => (
<motion.button
key={type.value}
whileHover={{ scale: 1.05, y: -2 }}
whileTap={{ scale: 0.95 }}
onClick={() => handleTypeChange(type.value)}
className={`px-6 py-3 rounded-xl border transition-all duration-300 flex items-center gap-3 ${
selectedType === type.value
? `bg-gradient-to-r ${type.color} border-transparent text-white font-semibold shadow-lg`
: 'border-gray-700 text-gray-400 hover:border-purple-500 hover:text-purple-400 bg-gray-900/50'
}`}
>
<span className="text-xl">{type.icon}</span>
<span>{type.label}</span>
</motion.button>
))}
</motion.div>
{/* Search and Filters */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
className="bg-gray-900/50 backdrop-blur-sm rounded-2xl border border-gray-800 p-6 mb-8"
>
{/* Search Bar */}
<div className="flex flex-col lg:flex-row gap-4 mb-6">
<div className="relative flex-1">
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400" size={20} />
<input
type="text"
placeholder="搜索技术文章..."
value={searchTerm}
onChange={(e) => handleSearchChange(e.target.value)}
className="w-full pl-12 pr-4 py-3 bg-gray-800/50 border border-gray-700 rounded-xl text-white placeholder-gray-400 focus:outline-none focus:border-purple-500 transition-colors"
/>
</div>
<div className="flex gap-4">
{/* Sort Dropdown */}
<div className="relative">
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
className="appearance-none bg-gray-800/50 border border-gray-700 rounded-xl px-4 py-3 text-white focus:outline-none focus:border-purple-500 pr-10"
>
{sortOptions.map(option => (
<option key={option.value} value={option.value}>{option.label}</option>
))}
</select>
<ChevronDown className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400" size={16} />
</div>
{/* View Mode Toggle */}
<div className="flex bg-gray-800/50 border border-gray-700 rounded-xl p-1">
<button
onClick={() => setViewMode('grid')}
className={`p-2 rounded-lg transition-colors ${
viewMode === 'grid' ? 'bg-purple-500 text-white' : 'text-gray-400 hover:text-white'
}`}
>
<Grid size={20} />
</button>
<button
onClick={() => setViewMode('list')}
className={`p-2 rounded-lg transition-colors ${
viewMode === 'list' ? 'bg-purple-500 text-white' : 'text-gray-400 hover:text-white'
}`}
>
<ListIcon size={20} />
</button>
</div>
</div>
</div>
{/* Category Filter */}
<div className="flex flex-wrap gap-3">
{categories.map((category) => (
<motion.button
key={category.name}
whileHover={{ scale: 1.05, y: -2 }}
whileTap={{ scale: 0.95 }}
onClick={() => handleCategoryChange(category.name)}
className={`px-4 py-2 rounded-full border transition-all duration-300 flex items-center gap-2 ${
selectedCategory === category.name
? 'bg-gradient-to-r from-purple-500 to-cyan-500 border-transparent text-white font-semibold'
: 'border-gray-700 text-gray-400 hover:border-purple-500 hover:text-purple-400'
}`}
>
<span className="text-lg">{category.icon}</span>
<span>{category.name}</span>
<span className="bg-gray-800 px-2 py-1 rounded-full text-xs">{category.count}</span>
</motion.button>
))}
</div>
</motion.div>
{/* Results Info */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.3 }}
className="flex items-center justify-between mb-8"
>
<div className="text-gray-400">
找到 <span className="text-purple-400 font-semibold">{filteredPosts.length}</span> 篇技术文章
{selectedCategory !== 'All Articles' && (
<span> 在分类 "<span className="text-purple-400">{selectedCategory}</span>" 中</span>
)}
{selectedType !== 'all' && (
<span> · 类型: "<span className="text-purple-400">{articleTypes.find(t => t.value === selectedType)?.label}</span>"</span>
)}
</div>
<Link
to="/"
className="flex items-center gap-2 text-gray-400 hover:text-purple-400 transition-colors"
>
<ArrowLeft size={16} />
返回首页
</Link>
</motion.div>
{/* Article Grid/List */}
<div className={`${
viewMode === 'grid'
? 'grid md:grid-cols-2 lg:grid-cols-3 gap-8'
: 'space-y-6'
}`}>
{filteredPosts.map((post, index) => (
<ArticleCard key={post.id} post={post} index={index} />
))}
</div>
{/* No Results */}
{filteredPosts.length === 0 && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="text-center py-20"
>
<div className="text-6xl mb-4">📄</div>
<h3 className="text-2xl font-bold text-white mb-2">未找到相关文章</h3>
<p className="text-gray-400 mb-6">尝试调整搜索关键词、文章类型或选择其他分类</p>
<button
onClick={() => {
setSearchTerm('');
setSelectedCategory('All Articles');
setSelectedType('all');
handleSearchChange('');
handleCategoryChange('All Articles');
handleTypeChange('all');
}}
className="px-6 py-3 bg-gradient-to-r from-purple-500 to-cyan-500 text-white font-semibold rounded-xl hover:shadow-lg hover:shadow-purple-500/30 transition-all duration-300"
>
重置筛选条件
</button>
</motion.div>
)}
</div>
</div>
);
};
export default ArticleList;
+52 -2
View File
@@ -20,6 +20,56 @@ import SectionTag, { SectionHeader } from '../components/SectionTags';
const getSectionTagType = (title: string): string | null => {
const titleLower = title.toLowerCase();
// 项目管理相关 - 优先级最高
if (titleLower.includes('项目') && titleLower.includes('背景')) {
return 'project-background';
}
if (titleLower.includes('实施') && (titleLower.includes('过程') || titleLower.includes('流程') || titleLower.includes('步骤'))) {
return 'implementation-process';
}
if (titleLower.includes('需求') && (titleLower.includes('分析') || titleLower.includes('梳理') || titleLower.includes('调研'))) {
return 'requirements-analysis';
}
if (titleLower.includes('方案') && (titleLower.includes('设计') || titleLower.includes('规划'))) {
return 'solution-design';
}
if (titleLower.includes('技术') && (titleLower.includes('实现') || titleLower.includes('开发') || titleLower.includes('编码'))) {
return 'technical-implementation';
}
if (titleLower.includes('测试') && (titleLower.includes('验证') || titleLower.includes('校验') || titleLower.includes('检验'))) {
return 'testing-validation';
}
if (titleLower.includes('部署') || titleLower.includes('上线') || titleLower.includes('发布')) {
return 'deployment-launch';
}
if (titleLower.includes('效果') && (titleLower.includes('评估') || titleLower.includes('评价'))) {
return 'results-evaluation';
}
if (titleLower.includes('经验') && (titleLower.includes('总结') || titleLower.includes('教训'))) {
return 'lessons-learned';
}
if (titleLower.includes('风险') && (titleLower.includes('管理') || titleLower.includes('控制') || titleLower.includes('防范'))) {
return 'risk-management';
}
if (titleLower.includes('资源') && (titleLower.includes('配置') || titleLower.includes('分配') || titleLower.includes('调配'))) {
return 'resource-allocation';
}
if (titleLower.includes('沟通') || titleLower.includes('协调') || titleLower.includes('配合')) {
return 'stakeholder-communication';
}
if (titleLower.includes('质量') && (titleLower.includes('保证') || titleLower.includes('管控') || titleLower.includes('控制'))) {
return 'quality-assurance';
}
if (titleLower.includes('性能') && (titleLower.includes('优化') || titleLower.includes('提升') || titleLower.includes('改进'))) {
return 'performance-optimization';
}
if (titleLower.includes('用户') && (titleLower.includes('培训') || titleLower.includes('指导'))) {
return 'user-training';
}
if (titleLower.includes('运维') || titleLower.includes('维护') || titleLower.includes('支持')) {
return 'maintenance-support';
}
// 专家相关
if (titleLower.includes('专家') && (titleLower.includes('评价') || titleLower.includes('评论') || titleLower.includes('观点'))) {
return 'expert-opinion';
@@ -29,7 +79,7 @@ const getSectionTagType = (title: string): string | null => {
}
// 结论相关
if (titleLower.includes('结论') || titleLower.includes('总结') || titleLower.includes('结语')) {
if (titleLower.includes('结论') || titleLower.includes('结语')) {
return 'conclusion';
}
if (titleLower.includes('总结') || titleLower.includes('小结')) {
@@ -50,7 +100,7 @@ const getSectionTagType = (title: string): string | null => {
}
// 技术相关
if (titleLower.includes('技术') && (titleLower.includes('细节') || titleLower.includes('实现') || titleLower.includes('原理'))) {
if (titleLower.includes('技术') && (titleLower.includes('细节') || titleLower.includes('原理'))) {
return 'technical-details';
}
if (titleLower.includes('架构') || titleLower.includes('系统设计')) {
+382
View File
@@ -0,0 +1,382 @@
import React, { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import { useTranslation } from 'react-i18next';
import { Link, useSearchParams } from 'react-router-dom';
import {
Calendar, Clock, ArrowRight, Tag, TrendingUp, User, Heart,
MessageCircle, Bookmark, Search, Filter, Grid, List as ListIcon,
ChevronDown, ArrowLeft
} from 'lucide-react';
import { blogPosts, BlogPost } from '../data/blogData';
const BlogList: React.FC = () => {
const { t } = useTranslation();
const [searchParams, setSearchParams] = useSearchParams();
const [searchTerm, setSearchTerm] = useState('');
const [selectedCategory, setSelectedCategory] = useState('All Posts');
const [sortBy, setSortBy] = useState('latest');
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
const [filteredPosts, setFilteredPosts] = useState<BlogPost[]>(blogPosts);
const [showFilters, setShowFilters] = useState(false);
const categories = [
{ name: 'All Posts', count: blogPosts.length, icon: '📚' },
{ name: 'AI健康监护', count: blogPosts.filter(p => p.category === 'AI健康监护').length, icon: '🤖' },
{ name: '智能穿戴', count: blogPosts.filter(p => p.category === '智能穿戴').length, icon: '⌚' },
{ name: '护理机器人', count: blogPosts.filter(p => p.category === '护理机器人').length, icon: '🦾' },
{ name: '数据平台', count: blogPosts.filter(p => p.category === '数据平台').length, icon: '📊' }
];
const sortOptions = [
{ value: 'latest', label: '最新发布' },
{ value: 'oldest', label: '最早发布' },
{ value: 'popular', label: '最受欢迎' },
{ value: 'trending', label: '热门推荐' }
];
useEffect(() => {
// Handle URL parameters
const category = searchParams.get('category');
const search = searchParams.get('search');
if (category) setSelectedCategory(category);
if (search) setSearchTerm(search);
}, [searchParams]);
useEffect(() => {
let filtered = [...blogPosts];
// Filter by category
if (selectedCategory !== 'All Posts') {
filtered = filtered.filter(post => post.category === selectedCategory);
}
// Filter by search term
if (searchTerm) {
filtered = filtered.filter(post =>
post.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
post.excerpt.toLowerCase().includes(searchTerm.toLowerCase()) ||
post.tags.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase()))
);
}
// Sort posts
switch (sortBy) {
case 'latest':
filtered.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
break;
case 'oldest':
filtered.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
break;
case 'popular':
filtered.sort((a, b) => (b.likes + b.comments) - (a.likes + a.comments));
break;
case 'trending':
filtered = filtered.filter(post => post.trending).concat(
filtered.filter(post => !post.trending)
);
break;
}
setFilteredPosts(filtered);
}, [selectedCategory, searchTerm, sortBy]);
const handleCategoryChange = (category: string) => {
setSelectedCategory(category);
const newParams = new URLSearchParams(searchParams);
if (category === 'All Posts') {
newParams.delete('category');
} else {
newParams.set('category', category);
}
setSearchParams(newParams);
};
const handleSearchChange = (term: string) => {
setSearchTerm(term);
const newParams = new URLSearchParams(searchParams);
if (term) {
newParams.set('search', term);
} else {
newParams.delete('search');
}
setSearchParams(newParams);
};
const BlogCard: React.FC<{ post: BlogPost; index: number }> = ({ post, index }) => (
<motion.article
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: index * 0.1 }}
whileHover={{ y: -5 }}
className={`group relative bg-gray-900/50 backdrop-blur-sm rounded-2xl overflow-hidden border border-gray-800 hover:border-cyan-500/50 transition-all duration-500 ${
viewMode === 'list' ? 'flex' : ''
}`}
>
{/* Trending Badge */}
{post.trending && (
<div className="absolute top-4 left-4 z-10 bg-gradient-to-r from-orange-500 to-red-500 text-white px-3 py-1 rounded-full text-xs font-semibold flex items-center gap-1">
<TrendingUp size={12} />
Trending
</div>
)}
{/* Image Container */}
<div className={`relative overflow-hidden ${viewMode === 'list' ? 'w-80 h-48' : 'h-64'}`}>
<img
src={post.image}
alt={post.title}
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-700"
/>
<div className="absolute inset-0 bg-gradient-to-t from-gray-900 via-gray-900/50 to-transparent opacity-80"></div>
{/* Category Badge */}
<div className="absolute top-4 right-4 bg-cyan-500/20 backdrop-blur-md text-cyan-400 px-4 py-2 rounded-full text-sm font-semibold border border-cyan-500/30">
{post.category}
</div>
{/* Bookmark Icon */}
<motion.button
whileHover={{ scale: 1.2 }}
whileTap={{ scale: 0.9 }}
className="absolute bottom-4 right-4 bg-gray-900/80 backdrop-blur-sm p-2 rounded-full text-gray-400 hover:text-cyan-400 transition-colors"
>
<Bookmark size={20} />
</motion.button>
</div>
{/* Content */}
<div className="p-6 flex-1">
{/* Tags */}
<div className="flex flex-wrap gap-2 mb-4">
{post.tags.slice(0, 3).map(tag => (
<span key={tag} className="text-xs px-3 py-1 bg-gray-800/50 text-gray-400 rounded-full flex items-center gap-1">
<Tag size={10} />
{tag}
</span>
))}
</div>
{/* Title */}
<h3 className="text-2xl font-bold mb-3 text-white group-hover:text-cyan-400 transition-colors duration-300 line-clamp-2">
{post.title}
</h3>
{/* Excerpt */}
<p className="text-gray-400 mb-4 line-clamp-3">
{post.excerpt}
</p>
{/* Meta Info */}
<div className="flex items-center justify-between text-sm text-gray-500 mb-4">
<div className="flex items-center gap-4">
<span className="flex items-center gap-1">
<User size={14} />
{post.author}
</span>
<span className="flex items-center gap-1">
<Calendar size={14} />
{post.date}
</span>
<span className="flex items-center gap-1">
<Clock size={14} />
{post.readTime}
</span>
</div>
</div>
{/* Engagement Stats */}
<div className="flex items-center justify-between pt-4 border-t border-gray-800">
<div className="flex items-center gap-4">
<button className="flex items-center gap-2 text-gray-400 hover:text-red-400 transition-colors">
<Heart size={18} />
<span>{post.likes}</span>
</button>
<button className="flex items-center gap-2 text-gray-400 hover:text-cyan-400 transition-colors">
<MessageCircle size={18} />
<span>{post.comments}</span>
</button>
</div>
{/* Read More Button */}
<Link
to={`/blog/${post.id}`}
className="flex items-center gap-2 text-cyan-400 font-semibold hover:text-cyan-300 transition-colors"
>
<motion.span
whileHover={{ x: 5 }}
className="flex items-center gap-2"
>
阅读更多
<ArrowRight size={16} />
</motion.span>
</Link>
</div>
</div>
{/* Hover Glow Effect */}
<div className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-500 pointer-events-none">
<div className="absolute inset-0 bg-gradient-to-r from-cyan-500/10 to-green-500/10"></div>
</div>
</motion.article>
);
return (
<div className="min-h-screen bg-gradient-to-b from-gray-900 via-black to-gray-900 pt-24">
{/* Header */}
<div className="container mx-auto px-4 py-12">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="text-center mb-12"
>
<h1 className="text-5xl font-bold mb-6 bg-gradient-to-r from-cyan-400 to-green-400 bg-clip-text text-transparent">
技术博客
</h1>
<p className="text-xl text-gray-400 max-w-3xl mx-auto">
探索AI健康科技的前沿洞察,分享创新技术与实践经验
</p>
</motion.div>
{/* Search and Filters */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
className="bg-gray-900/50 backdrop-blur-sm rounded-2xl border border-gray-800 p-6 mb-8"
>
{/* Search Bar */}
<div className="flex flex-col lg:flex-row gap-4 mb-6">
<div className="relative flex-1">
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400" size={20} />
<input
type="text"
placeholder="搜索博客文章..."
value={searchTerm}
onChange={(e) => handleSearchChange(e.target.value)}
className="w-full pl-12 pr-4 py-3 bg-gray-800/50 border border-gray-700 rounded-xl text-white placeholder-gray-400 focus:outline-none focus:border-cyan-500 transition-colors"
/>
</div>
<div className="flex gap-4">
{/* Sort Dropdown */}
<div className="relative">
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
className="appearance-none bg-gray-800/50 border border-gray-700 rounded-xl px-4 py-3 text-white focus:outline-none focus:border-cyan-500 pr-10"
>
{sortOptions.map(option => (
<option key={option.value} value={option.value}>{option.label}</option>
))}
</select>
<ChevronDown className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400" size={16} />
</div>
{/* View Mode Toggle */}
<div className="flex bg-gray-800/50 border border-gray-700 rounded-xl p-1">
<button
onClick={() => setViewMode('grid')}
className={`p-2 rounded-lg transition-colors ${
viewMode === 'grid' ? 'bg-cyan-500 text-black' : 'text-gray-400 hover:text-white'
}`}
>
<Grid size={20} />
</button>
<button
onClick={() => setViewMode('list')}
className={`p-2 rounded-lg transition-colors ${
viewMode === 'list' ? 'bg-cyan-500 text-black' : 'text-gray-400 hover:text-white'
}`}
>
<ListIcon size={20} />
</button>
</div>
</div>
</div>
{/* Category Filter */}
<div className="flex flex-wrap gap-3">
{categories.map((category) => (
<motion.button
key={category.name}
whileHover={{ scale: 1.05, y: -2 }}
whileTap={{ scale: 0.95 }}
onClick={() => handleCategoryChange(category.name)}
className={`px-4 py-2 rounded-full border transition-all duration-300 flex items-center gap-2 ${
selectedCategory === category.name
? 'bg-gradient-to-r from-cyan-500 to-green-500 border-transparent text-black font-semibold'
: 'border-gray-700 text-gray-400 hover:border-cyan-500 hover:text-cyan-400'
}`}
>
<span className="text-lg">{category.icon}</span>
<span>{category.name}</span>
<span className="bg-gray-800 px-2 py-1 rounded-full text-xs">{category.count}</span>
</motion.button>
))}
</div>
</motion.div>
{/* Results Info */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.2 }}
className="flex items-center justify-between mb-8"
>
<div className="text-gray-400">
找到 <span className="text-cyan-400 font-semibold">{filteredPosts.length}</span> 篇文章
{selectedCategory !== 'All Posts' && (
<span> 在分类 "<span className="text-cyan-400">{selectedCategory}</span>" 中</span>
)}
</div>
<Link
to="/"
className="flex items-center gap-2 text-gray-400 hover:text-cyan-400 transition-colors"
>
<ArrowLeft size={16} />
返回首页
</Link>
</motion.div>
{/* Blog Grid/List */}
<div className={`${
viewMode === 'grid'
? 'grid md:grid-cols-2 lg:grid-cols-3 gap-8'
: 'space-y-6'
}`}>
{filteredPosts.map((post, index) => (
<BlogCard key={post.id} post={post} index={index} />
))}
</div>
{/* No Results */}
{filteredPosts.length === 0 && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="text-center py-20"
>
<div className="text-6xl mb-4">📝</div>
<h3 className="text-2xl font-bold text-white mb-2">未找到相关文章</h3>
<p className="text-gray-400 mb-6">尝试调整搜索关键词或选择其他分类</p>
<button
onClick={() => {
setSearchTerm('');
setSelectedCategory('All Posts');
handleSearchChange('');
handleCategoryChange('All Posts');
}}
className="px-6 py-3 bg-gradient-to-r from-cyan-500 to-green-500 text-black font-semibold rounded-xl hover:shadow-lg hover:shadow-cyan-500/30 transition-all duration-300"
>
重置筛选条件
</button>
</motion.div>
)}
</div>
</div>
);
};
export default BlogList;
+546
View File
@@ -0,0 +1,546 @@
import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import {
Book, FileText, Code, Database, Shield, Cpu,
Search, ChevronRight, Download, ExternalLink,
Star, GitBranch, Package, Terminal, Layers,
Zap, Lock, Cloud, Settings, PlayCircle, CheckCircle,
ArrowRight, Clock, ArrowLeft, Bookmark, Users,
Globe, MessageCircle, Lightbulb, TrendingUp,
GraduationCap, Video, Headphones, HelpCircle
} from 'lucide-react';
const DocsHome: React.FC = () => {
const { t } = useTranslation();
const [selectedCategory, setSelectedCategory] = useState('getting-started');
const [searchQuery, setSearchQuery] = useState('');
const docCategories = [
{
id: 'getting-started',
name: '快速开始',
icon: <PlayCircle size={20} />,
color: 'from-cyan-500 to-blue-500',
description: '新手入门指南,快速上手我们的平台',
docs: [
{ title: '5分钟快速入门', time: '5 min', difficulty: 'Beginner', completed: true, description: '了解平台基础功能和核心概念' },
{ title: '安装与配置', time: '10 min', difficulty: 'Beginner', completed: true, description: '完整的安装配置流程指南' },
{ title: '环境配置详解', time: '15 min', difficulty: 'Intermediate', completed: false, description: '深入了解开发环境的配置方法' },
{ title: '第一个项目', time: '20 min', difficulty: 'Beginner', completed: false, description: '手把手创建你的第一个健康监控项目' }
]
},
{
id: 'api-reference',
name: 'API 参考',
icon: <Code size={20} />,
color: 'from-green-500 to-emerald-500',
description: '完整的API文档和代码示例',
docs: [
{ title: 'REST API 文档', time: '30 min', difficulty: 'Advanced', completed: false, description: '完整的REST API接口文档和参数说明' },
{ title: 'GraphQL API', time: '25 min', difficulty: 'Advanced', completed: false, description: 'GraphQL查询语言接口使用指南' },
{ title: 'WebSocket 实时API', time: '20 min', difficulty: 'Intermediate', completed: false, description: '实时数据传输WebSocket接口' },
{ title: 'SDK 开发文档', time: '45 min', difficulty: 'Intermediate', completed: false, description: '多语言SDK使用和集成指南' }
]
},
{
id: 'integrations',
name: '集成指南',
icon: <Package size={20} />,
color: 'from-purple-500 to-pink-500',
description: '第三方服务和平台集成方案',
docs: [
{ title: '第三方服务集成', time: '15 min', difficulty: 'Intermediate', completed: false, description: '常见第三方服务的集成方法' },
{ title: '医院信息系统对接', time: '35 min', difficulty: 'Advanced', completed: false, description: 'HIS系统集成和数据同步' },
{ title: 'IoT设备连接', time: '25 min', difficulty: 'Intermediate', completed: false, description: '智能设备和传感器连接指南' },
{ title: '云平台部署', time: '30 min', difficulty: 'Advanced', completed: false, description: '主流云平台部署和配置' }
]
},
{
id: 'security',
name: '安全指南',
icon: <Shield size={20} />,
color: 'from-red-500 to-orange-500',
description: '数据安全和隐私保护最佳实践',
docs: [
{ title: '身份认证机制', time: '20 min', difficulty: 'Advanced', completed: false, description: '多种身份认证方式的实现' },
{ title: '数据加密传输', time: '25 min', difficulty: 'Advanced', completed: false, description: '端到端数据加密和安全传输' },
{ title: 'HIPAA合规指南', time: '40 min', difficulty: 'Expert', completed: false, description: '医疗数据合规性要求和实现' },
{ title: '安全最佳实践', time: '30 min', difficulty: 'Intermediate', completed: false, description: '平台安全使用的最佳实践' }
]
},
{
id: 'tutorials',
name: '教程指南',
icon: <Book size={20} />,
color: 'from-yellow-500 to-amber-500',
description: '实战项目教程和案例分析',
docs: [
{ title: '健康应用开发实战', time: '60 min', difficulty: 'Intermediate', completed: false, description: '从零开始构建完整的健康监控应用' },
{ title: '数据分析与可视化', time: '45 min', difficulty: 'Advanced', completed: false, description: '健康数据的深度分析和图表展示' },
{ title: '护理机器人编程', time: '90 min', difficulty: 'Expert', completed: false, description: '智能护理机器人的程序开发' },
{ title: '可穿戴设备集成', time: '35 min', difficulty: 'Intermediate', completed: false, description: '智能手表和健康手环的数据集成' }
]
}
];
const featuredDocs = [
{
title: '快速入门指南',
description: '5分钟了解平台核心功能,开始你的健康科技之旅',
icon: <Zap className="text-yellow-400" />,
link: '#getting-started',
badge: '热门',
badgeColor: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30',
category: 'getting-started'
},
{
title: 'API 安全认证',
description: '了解如何安全地集成我们的API,保护用户数据隐私',
icon: <Lock className="text-cyan-400" />,
link: '#security',
badge: '必读',
badgeColor: 'bg-cyan-500/20 text-cyan-400 border-cyan-500/30',
category: 'security'
},
{
title: '云端部署指南',
description: '在主流云平台上部署和扩展你的健康应用',
icon: <Cloud className="text-green-400" />,
link: '#integrations',
badge: '最新',
badgeColor: 'bg-green-500/20 text-green-400 border-green-500/30',
category: 'integrations'
}
];
const quickActions = [
{
title: '下载SDK',
description: '获取最新的开发工具包',
icon: <Download size={20} />,
color: 'from-blue-500 to-cyan-500',
action: () => alert('下载SDK功能开发中...')
},
{
title: '在线演示',
description: '体验完整的功能演示',
icon: <Video size={20} />,
color: 'from-purple-500 to-pink-500',
action: () => alert('在线演示即将开放...')
},
{
title: '技术支持',
description: '获取专业技术帮助',
icon: <Headphones size={20} />,
color: 'from-green-500 to-emerald-500',
action: () => alert('正在连接技术支持...')
},
{
title: '社区论坛',
description: '加入开发者社区',
icon: <Users size={20} />,
color: 'from-orange-500 to-red-500',
action: () => alert('社区论坛即将上线...')
}
];
const recentUpdates = [
{
title: 'API v2.1 发布',
description: '新增实时健康监控接口',
date: '2024-03-15',
type: 'update'
},
{
title: 'WebSocket 连接优化',
description: '提升实时数据传输性能',
date: '2024-03-12',
type: 'improvement'
},
{
title: 'Python SDK 更新',
description: '支持最新的机器学习模型',
date: '2024-03-10',
type: 'new'
}
];
const currentCategory = docCategories.find(cat => cat.id === selectedCategory);
const handleFeaturedDocClick = (doc: any) => {
setSelectedCategory(doc.category);
};
const filteredDocs = currentCategory?.docs.filter(doc =>
doc.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
doc.description.toLowerCase().includes(searchQuery.toLowerCase())
) || [];
return (
<div className="min-h-screen bg-gradient-to-b from-gray-900 via-black to-gray-900 pt-24">
{/* Header */}
<div className="container mx-auto px-4 py-12">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="text-center mb-12"
>
<h1 className="text-5xl font-bold mb-6 bg-gradient-to-r from-cyan-400 to-green-400 bg-clip-text text-transparent">
开发者文档
</h1>
<p className="text-xl text-gray-400 max-w-3xl mx-auto">
完整的API文档、集成指南和最佳实践,助您快速构建创新的健康科技应用
</p>
<div className="flex items-center justify-center gap-4 mt-8">
<Link
to="/"
className="flex items-center gap-2 text-gray-400 hover:text-cyan-400 transition-colors"
>
<ArrowLeft size={16} />
返回首页
</Link>
<span className="text-gray-600">•</span>
<a
href="/website/#docs"
className="flex items-center gap-2 text-gray-400 hover:text-cyan-400 transition-colors"
>
<Book size={16} />
首页文档区域
</a>
</div>
</motion.div>
{/* Search Bar */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
className="max-w-2xl mx-auto mb-12"
>
<div className="relative">
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400" size={20} />
<input
type="text"
placeholder="搜索文档、API或教程..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-12 pr-20 py-4 bg-gray-900/50 backdrop-blur-sm border border-gray-800 rounded-2xl text-white placeholder-gray-500 focus:border-cyan-500 focus:outline-none transition-all duration-300"
/>
<kbd className="absolute right-4 top-1/2 transform -translate-y-1/2 px-3 py-1 bg-gray-800 text-gray-400 rounded-lg text-sm">
⌘K
</kbd>
</div>
{searchQuery && (
<div className="mt-2 text-sm text-gray-400">
在 "{currentCategory?.name}" 中找到 {filteredDocs.length} 个结果
</div>
)}
</motion.div>
{/* Quick Actions */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
className="grid md:grid-cols-4 gap-4 mb-12"
>
{quickActions.map((action, index) => (
<motion.button
key={action.title}
onClick={action.action}
whileHover={{ y: -3, scale: 1.02 }}
whileTap={{ scale: 0.98 }}
className={`p-4 bg-gradient-to-r ${action.color} rounded-xl text-white text-left group transition-all duration-300 shadow-lg hover:shadow-xl`}
>
<div className="flex items-center gap-3 mb-2">
{action.icon}
<span className="font-semibold">{action.title}</span>
</div>
<p className="text-sm opacity-90">{action.description}</p>
</motion.button>
))}
</motion.div>
{/* Featured Documentation */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
className="mb-12"
>
<h2 className="text-2xl font-bold text-white mb-6 flex items-center gap-2">
<Star className="text-yellow-400" size={24} />
精选文档
</h2>
<div className="grid md:grid-cols-3 gap-6">
{featuredDocs.map((doc, index) => (
<motion.button
key={doc.title}
onClick={() => handleFeaturedDocClick(doc)}
whileHover={{ y: -5, scale: 1.02 }}
className="text-left relative bg-gradient-to-br from-gray-900/80 to-gray-800/50 backdrop-blur-sm p-6 rounded-2xl border border-gray-800 hover:border-cyan-500/50 transition-all duration-300 group overflow-hidden"
>
<div className="absolute inset-0 opacity-10">
<div className="absolute inset-0 bg-gradient-to-br from-cyan-500/20 to-green-500/20"></div>
</div>
<div className="relative z-10">
<div className="flex items-start justify-between mb-4">
<div className="p-3 bg-gray-800/50 rounded-xl">
{doc.icon}
</div>
<span className={`px-3 py-1 rounded-full text-xs font-semibold border ${doc.badgeColor}`}>
{doc.badge}
</span>
</div>
<h3 className="text-xl font-bold text-white mb-2 group-hover:text-cyan-400 transition-colors">
{doc.title}
</h3>
<p className="text-gray-400 text-sm mb-4">
{doc.description}
</p>
<div className="flex items-center gap-2 text-cyan-400 hover:text-cyan-300 transition-colors">
立即查看
<ArrowRight size={16} />
</div>
</div>
</motion.button>
))}
</div>
</motion.div>
{/* Main Documentation Grid */}
<div className="grid lg:grid-cols-4 gap-8">
{/* Sidebar Categories */}
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.4 }}
className="lg:col-span-1"
>
<div className="sticky top-4 space-y-6">
{/* Categories */}
<div className="space-y-2">
<h3 className="text-lg font-semibold text-white mb-4">文档分类</h3>
{docCategories.map((category) => (
<motion.button
key={category.id}
onClick={() => setSelectedCategory(category.id)}
whileHover={{ x: 5 }}
whileTap={{ scale: 0.98 }}
className={`w-full text-left px-4 py-3 rounded-xl transition-all duration-300 ${
selectedCategory === category.id
? 'bg-gradient-to-r ' + category.color + ' text-white shadow-lg'
: 'bg-gray-900/30 text-gray-400 hover:bg-gray-800/50 hover:text-white'
}`}
>
<div className="flex items-center gap-3 mb-1">
<span className={selectedCategory === category.id ? 'text-white' : 'text-gray-500'}>
{category.icon}
</span>
<span className="font-medium">{category.name}</span>
<span className="ml-auto bg-gray-800/50 px-2 py-1 rounded-full text-xs">
{category.docs.length}
</span>
</div>
<p className="text-xs opacity-80 ml-8">{category.description}</p>
</motion.button>
))}
</div>
{/* Recent Updates */}
<div className="p-4 bg-gray-900/30 rounded-xl border border-gray-800">
<h4 className="text-sm font-semibold text-gray-400 mb-3 flex items-center gap-2">
<TrendingUp size={16} />
最近更新
</h4>
<div className="space-y-3">
{recentUpdates.map((update, index) => (
<div key={index} className="text-sm">
<div className="flex items-center gap-2 mb-1">
<span className={`w-2 h-2 rounded-full ${
update.type === 'new' ? 'bg-green-400' :
update.type === 'update' ? 'bg-blue-400' : 'bg-yellow-400'
}`}></span>
<span className="text-white font-medium">{update.title}</span>
</div>
<p className="text-gray-500 ml-4">{update.description}</p>
<p className="text-gray-600 text-xs ml-4 mt-1">{update.date}</p>
</div>
))}
</div>
</div>
{/* Quick Links */}
<div className="p-4 bg-gray-900/30 rounded-xl border border-gray-800">
<h4 className="text-sm font-semibold text-gray-400 mb-3">快速链接</h4>
<div className="space-y-2">
<a href="#" className="flex items-center gap-2 text-gray-500 hover:text-cyan-400 transition-colors">
<GitBranch size={16} />
<span className="text-sm">GitHub 仓库</span>
</a>
<a href="#" className="flex items-center gap-2 text-gray-500 hover:text-cyan-400 transition-colors">
<Terminal size={16} />
<span className="text-sm">CLI 工具</span>
</a>
<a href="#" className="flex items-center gap-2 text-gray-500 hover:text-cyan-400 transition-colors">
<Settings size={16} />
<span className="text-sm">API 密钥</span>
</a>
<a href="#" className="flex items-center gap-2 text-gray-500 hover:text-cyan-400 transition-colors">
<MessageCircle size={16} />
<span className="text-sm">开发者论坛</span>
</a>
</div>
</div>
</div>
</motion.div>
{/* Documentation Content */}
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.5 }}
className="lg:col-span-3"
>
<AnimatePresence mode="wait">
{currentCategory && (
<motion.div
key={selectedCategory}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
className="space-y-6"
>
{/* Category Header */}
<div className="mb-8">
<div className={`inline-flex items-center gap-3 px-6 py-3 rounded-2xl bg-gradient-to-r ${currentCategory.color} shadow-lg`}>
{currentCategory.icon}
<div>
<h3 className="text-2xl font-bold text-white">{currentCategory.name}</h3>
<p className="text-white/80 text-sm">{currentCategory.description}</p>
</div>
</div>
</div>
{/* Documentation Items */}
{filteredDocs.map((doc, index) => (
<motion.div
key={doc.title}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3, delay: index * 0.1 }}
whileHover={{ x: 5 }}
className="group bg-gray-900/50 backdrop-blur-sm border border-gray-800 rounded-xl p-6 hover:border-cyan-500/50 transition-all duration-300"
>
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-3 mb-3">
<FileText className="text-gray-500" size={20} />
<h4 className="text-xl font-semibold text-white group-hover:text-cyan-400 transition-colors">
{doc.title}
</h4>
{doc.completed && (
<CheckCircle className="text-green-400" size={20} />
)}
</div>
<div className="flex items-center gap-4 text-sm text-gray-500 mb-3">
<span className="flex items-center gap-1">
<Clock size={14} />
{doc.time}
</span>
<span className={`px-2 py-1 rounded-full text-xs font-medium ${
doc.difficulty === 'Beginner' ? 'bg-green-500/20 text-green-400' :
doc.difficulty === 'Intermediate' ? 'bg-yellow-500/20 text-yellow-400' :
doc.difficulty === 'Advanced' ? 'bg-orange-500/20 text-orange-400' :
'bg-red-500/20 text-red-400'
}`}>
{doc.difficulty}
</span>
</div>
<p className="text-gray-400 mb-4">
{doc.description}
</p>
<div className="flex items-center gap-4">
<button className="flex items-center gap-2 text-cyan-400 hover:text-cyan-300 transition-colors">
<Book size={16} />
<span className="text-sm font-medium">阅读文档</span>
</button>
<button className="flex items-center gap-2 text-gray-500 hover:text-gray-400 transition-colors">
<Download size={16} />
<span className="text-sm">下载 PDF</span>
</button>
<button className="flex items-center gap-2 text-gray-500 hover:text-gray-400 transition-colors">
<Bookmark size={16} />
<span className="text-sm">收藏</span>
</button>
</div>
</div>
<div className="ml-4">
<motion.div
whileHover={{ rotate: 180 }}
transition={{ duration: 0.3 }}
className="p-3 bg-gray-800/50 rounded-xl group-hover:bg-cyan-500/20 transition-colors"
>
<ChevronRight className="text-gray-500 group-hover:text-cyan-400" size={20} />
</motion.div>
</div>
</div>
</motion.div>
))}
{/* No results message */}
{searchQuery && filteredDocs.length === 0 && (
<div className="text-center py-12">
<HelpCircle className="w-16 h-16 text-gray-600 mx-auto mb-4" />
<h3 className="text-xl font-semibold text-white mb-2">未找到相关文档</h3>
<p className="text-gray-400 mb-4">尝试调整搜索关键词或浏览其他分类</p>
<button
onClick={() => setSearchQuery('')}
className="px-4 py-2 bg-cyan-500 text-black font-semibold rounded-lg hover:bg-cyan-400 transition-colors"
>
清除搜索
</button>
</div>
)}
</motion.div>
)}
</AnimatePresence>
{/* Help Section */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.6 }}
className="mt-12 p-8 bg-gradient-to-r from-cyan-900/20 to-green-900/20 backdrop-blur-sm rounded-2xl border border-cyan-500/30"
>
<div className="flex items-center justify-between">
<div>
<h3 className="text-2xl font-bold text-white mb-2">需要帮助?</h3>
<p className="text-gray-400">我们的技术团队随时为您提供支持,解答您的技术疑问</p>
</div>
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="px-6 py-3 bg-gradient-to-r from-cyan-500 to-green-500 text-black font-bold rounded-xl hover:shadow-lg hover:shadow-cyan-500/30 transition-all duration-300"
>
联系技术支持
</motion.button>
</div>
</motion.div>
</motion.div>
</div>
</div>
</div>
);
};
export default DocsHome;