mirror of
https://github.com/iflytek/skillhub.git
synced 2026-10-06 02:48:28 +00:00
Merge branch 'feature/homepage-redesign' into feature/project-init
This commit is contained in:
commit
1a5bc12fb7
8 changed files with 6476 additions and 3 deletions
6
package-lock.json
generated
Normal file
6
package-lock.json
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "homepage-redesign",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
||||
75
web/LANDING_PAGE_REDESIGN.md
Normal file
75
web/LANDING_PAGE_REDESIGN.md
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# SkillHub 落地页重新设计
|
||||
|
||||
## 设计理念
|
||||
|
||||
采用"技术编织"(Tech Weave)美学风格,通过以下元素传达 SkillHub 作为企业级技能注册中心的专业性和创新性:
|
||||
|
||||
### 视觉特点
|
||||
|
||||
1. **动态粒子系统**
|
||||
- Canvas 实现的粒子连接动画
|
||||
- 象征技能之间的连接和协作
|
||||
- 80个粒子节点,动态连线效果
|
||||
|
||||
2. **配色方案**
|
||||
- 深蓝紫色基调(slate-950, indigo-950)
|
||||
- 霓虹青色点缀(cyan-500)
|
||||
- 紫罗兰色辅助(violet-500)
|
||||
- 营造科技感和未来感
|
||||
|
||||
3. **字体选择**
|
||||
- 标题:Syne(几何感强,现代)
|
||||
- 正文:IBM Plex Sans(技术感,易读)
|
||||
- 代码:JetBrains Mono
|
||||
|
||||
4. **动效设计**
|
||||
- 渐入动画(fade-up, fade-in)
|
||||
- 悬浮卡片效果(hover:scale-105)
|
||||
- 渐变光晕(gradient glow)
|
||||
- 脉冲动画(pulse)
|
||||
|
||||
### 内容结构
|
||||
|
||||
1. **Hero 区域**
|
||||
- 大标题 + 渐变文字效果
|
||||
- 搜索栏(带光晕效果)
|
||||
- CTA 按钮(探索技能 / 发布技能)
|
||||
- 统计数据展示(技能包、下载量、团队)
|
||||
|
||||
2. **特性展示**
|
||||
- 6个核心特性卡片
|
||||
- 图标 + 标题 + 描述
|
||||
- 悬浮交互效果
|
||||
|
||||
3. **CTA 区域**
|
||||
- 快速开始指引
|
||||
- 命令行示例
|
||||
- 行动按钮
|
||||
|
||||
4. **页脚**
|
||||
- 版权信息
|
||||
- 导航链接
|
||||
|
||||
## 技术实现
|
||||
|
||||
- React + TypeScript
|
||||
- TailwindCSS
|
||||
- Canvas API(粒子动画)
|
||||
- TanStack Router
|
||||
|
||||
## 文件变更
|
||||
|
||||
- 新增:`web/src/pages/landing.tsx` - 新的落地页组件
|
||||
- 修改:`web/src/app/router.tsx` - 路由配置,将 landing 设为首页
|
||||
- 修改:`web/index.html` - 更新字体引用
|
||||
- 修改:`web/src/index.css` - 更新字体配置
|
||||
|
||||
## 本地预览
|
||||
|
||||
```bash
|
||||
cd web
|
||||
npm install --legacy-peer-deps
|
||||
npm run dev
|
||||
```
|
||||
|
||||
访问 `http://localhost:5173` 查看新的落地页。
|
||||
55
web/PREVIEW.md
Normal file
55
web/PREVIEW.md
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# 落地页预览说明
|
||||
|
||||
## 如何查看
|
||||
|
||||
1. 启动开发服务器:
|
||||
```bash
|
||||
cd web
|
||||
npm run dev
|
||||
```
|
||||
|
||||
2. 访问 `http://localhost:5173`
|
||||
|
||||
## 页面特性
|
||||
|
||||
### 动态效果
|
||||
- 背景粒子动画会自动运行
|
||||
- 鼠标悬停在特性卡片上会有缩放和颜色变化
|
||||
- 按钮有渐变和阴影效果
|
||||
- 搜索栏有光晕效果
|
||||
|
||||
### 响应式设计
|
||||
- 移动端:单列布局,较小字体
|
||||
- 平板:2列特性卡片
|
||||
- 桌面:3列特性卡片,大标题
|
||||
|
||||
### 交互元素
|
||||
- "探索技能" 按钮 → 跳转到搜索页面
|
||||
- "发布技能" 按钮 → 跳转到发布页面
|
||||
- 搜索栏 → 输入后跳转到搜索结果
|
||||
- 统计数字 → 悬停有渐变效果
|
||||
|
||||
## 与原首页的区别
|
||||
|
||||
| 特性 | 原首页 | 新落地页 |
|
||||
|------|--------|----------|
|
||||
| 主要内容 | 技能列表(热门/最新) | 产品介绍和特性展示 |
|
||||
| 视觉风格 | 简洁卡片式 | 科技感渐变 + 粒子动画 |
|
||||
| 配色 | 浅色为主 | 深色主题 + 霓虹色 |
|
||||
| 目标 | 快速浏览技能 | 吸引新用户了解产品 |
|
||||
| 路由 | `/` | `/`(新),原首页移至 `/skills` |
|
||||
|
||||
## 设计亮点
|
||||
|
||||
1. **粒子连接动画**:80个粒子节点,距离小于120px时自动连线,透明度随距离变化
|
||||
2. **渐变文字**:标题使用 cyan → blue → violet 三色渐变
|
||||
3. **光晕效果**:搜索栏和背景有模糊的光晕装饰
|
||||
4. **分层动画**:不同元素有不同的延迟时间,形成层次感
|
||||
5. **悬浮交互**:卡片悬停时会上浮并显示边框高亮
|
||||
|
||||
## 技术细节
|
||||
|
||||
- 粒子系统使用 Canvas 2D API
|
||||
- 动画使用 CSS animations 和 transitions
|
||||
- 响应式使用 Tailwind 的 breakpoint 系统
|
||||
- 字体通过 Google Fonts CDN 加载
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
<title>SkillHub</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300..700;1,9..40,300..700&family=Outfit:wght@300;400;500;600;700&family=Playfair+Display:ital,wght@0,400..900;1,400..900&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=IBM+Plex+Sans:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
|
|
|||
6035
web/package-lock.json
generated
Normal file
6035
web/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,6 @@
|
|||
import { createRouter, createRoute, createRootRoute, redirect } from '@tanstack/react-router'
|
||||
import { Layout } from './layout'
|
||||
import { LandingPage } from '@/pages/landing'
|
||||
import { HomePage } from '@/pages/home'
|
||||
import { LoginPage } from '@/pages/login'
|
||||
import { DashboardPage } from '@/pages/dashboard'
|
||||
|
|
@ -24,6 +25,12 @@ const rootRoute = createRootRoute({
|
|||
const homeRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: '/',
|
||||
component: LandingPage,
|
||||
})
|
||||
|
||||
const skillsRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: '/skills',
|
||||
component: HomePage,
|
||||
})
|
||||
|
||||
|
|
@ -189,6 +196,7 @@ const adminAuditLogRoute = createRoute({
|
|||
|
||||
const routeTree = rootRoute.addChildren([
|
||||
homeRoute,
|
||||
skillsRoute,
|
||||
loginRoute,
|
||||
searchRoute,
|
||||
namespaceRoute,
|
||||
|
|
|
|||
|
|
@ -75,11 +75,11 @@
|
|||
|
||||
body {
|
||||
@apply bg-background text-foreground antialiased;
|
||||
font-family: 'DM Sans', system-ui, sans-serif;
|
||||
font-family: 'IBM Plex Sans', system-ui, sans-serif;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: 'Outfit', 'DM Sans', system-ui, sans-serif;
|
||||
font-family: 'Syne', 'IBM Plex Sans', system-ui, sans-serif;
|
||||
}
|
||||
|
||||
code, pre, kbd {
|
||||
|
|
|
|||
294
web/src/pages/landing.tsx
Normal file
294
web/src/pages/landing.tsx
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
import { useNavigate } from '@tanstack/react-router'
|
||||
import { SearchBar } from '@/features/search/search-bar'
|
||||
import { Button } from '@/shared/ui/button'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
export function LandingPage() {
|
||||
const navigate = useNavigate()
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
const [stats] = useState({
|
||||
skills: '1000+',
|
||||
downloads: '50K+',
|
||||
teams: '200+',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current
|
||||
if (!canvas) return
|
||||
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) return
|
||||
|
||||
const resize = () => {
|
||||
if (canvas) {
|
||||
canvas.width = window.innerWidth
|
||||
canvas.height = window.innerHeight
|
||||
}
|
||||
}
|
||||
resize()
|
||||
window.addEventListener('resize', resize)
|
||||
|
||||
class Particle {
|
||||
x: number
|
||||
y: number
|
||||
vx: number
|
||||
vy: number
|
||||
radius: number
|
||||
|
||||
constructor(canvasWidth: number, canvasHeight: number) {
|
||||
this.x = Math.random() * canvasWidth
|
||||
this.y = Math.random() * canvasHeight
|
||||
this.vx = (Math.random() - 0.5) * 0.3
|
||||
this.vy = (Math.random() - 0.5) * 0.3
|
||||
this.radius = Math.random() * 1.5 + 0.5
|
||||
}
|
||||
|
||||
update(canvasWidth: number, canvasHeight: number) {
|
||||
this.x += this.vx
|
||||
this.y += this.vy
|
||||
if (this.x < 0 || this.x > canvasWidth) this.vx *= -1
|
||||
if (this.y < 0 || this.y > canvasHeight) this.vy *= -1
|
||||
}
|
||||
|
||||
draw(context: CanvasRenderingContext2D) {
|
||||
context.beginPath()
|
||||
context.arc(this.x, this.y, this.radius, 0, Math.PI * 2)
|
||||
context.fillStyle = 'rgba(56, 189, 248, 0.6)'
|
||||
context.fill()
|
||||
}
|
||||
}
|
||||
|
||||
const particles: Particle[] = []
|
||||
const particleCount = 80
|
||||
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
particles.push(new Particle(canvas.width, canvas.height))
|
||||
}
|
||||
|
||||
const connectParticles = () => {
|
||||
for (let i = 0; i < particles.length; i++) {
|
||||
for (let j = i + 1; j < particles.length; j++) {
|
||||
const dx = particles[i].x - particles[j].x
|
||||
const dy = particles[i].y - particles[j].y
|
||||
const distance = Math.sqrt(dx * dx + dy * dy)
|
||||
|
||||
if (distance < 120) {
|
||||
ctx.beginPath()
|
||||
const opacity = 0.15 * (1 - distance / 120)
|
||||
ctx.strokeStyle = 'rgba(56, 189, 248, ' + opacity + ')'
|
||||
ctx.lineWidth = 0.5
|
||||
ctx.moveTo(particles[i].x, particles[i].y)
|
||||
ctx.lineTo(particles[j].x, particles[j].y)
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const animate = () => {
|
||||
if (!canvas) return
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
particles.forEach(particle => {
|
||||
particle.update(canvas.width, canvas.height)
|
||||
particle.draw(ctx)
|
||||
})
|
||||
|
||||
connectParticles()
|
||||
requestAnimationFrame(animate)
|
||||
}
|
||||
|
||||
animate()
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', resize)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleSearch = (query: string) => {
|
||||
navigate({ to: '/search', search: { q: query, sort: 'relevance', page: 1 } })
|
||||
}
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: '🔒',
|
||||
title: '私有部署',
|
||||
description: '完全自托管,数据主权在您手中。一键部署到您的基础设施,防火墙后安全运行。',
|
||||
},
|
||||
{
|
||||
icon: '📦',
|
||||
title: '版本管理',
|
||||
description: '语义化版本控制,自定义标签(beta、stable),自动追踪 latest 版本。',
|
||||
},
|
||||
{
|
||||
icon: '🔍',
|
||||
title: '智能搜索',
|
||||
description: '全文搜索,支持命名空间、下载量、评分、时间等多维度筛选。',
|
||||
},
|
||||
{
|
||||
icon: '👥',
|
||||
title: '团队协作',
|
||||
description: '命名空间管理,角色权限控制(Owner/Admin/Member),发布策略配置。',
|
||||
},
|
||||
{
|
||||
icon: '✅',
|
||||
title: '审核治理',
|
||||
description: '团队内审核,平台级审批,全流程审计日志,满足合规要求。',
|
||||
},
|
||||
{
|
||||
icon: '⚡',
|
||||
title: 'CLI 优先',
|
||||
description: '原生 REST API,兼容现有 ClawHub CLI 工具,无需客户端改动。',
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="relative min-h-screen overflow-hidden bg-gradient-to-br from-slate-950 via-indigo-950 to-slate-900">
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="absolute inset-0 pointer-events-none"
|
||||
style={{ opacity: 0.4 }}
|
||||
/>
|
||||
|
||||
<div className="absolute top-0 left-1/4 w-96 h-96 bg-cyan-500/20 rounded-full blur-3xl" style={{ animation: 'pulse 4s ease-in-out infinite' }} />
|
||||
<div className="absolute bottom-0 right-1/4 w-96 h-96 bg-violet-500/20 rounded-full blur-3xl" style={{ animation: 'pulse 4s ease-in-out infinite 2s' }} />
|
||||
|
||||
<div className="relative z-10 container mx-auto px-6 py-20">
|
||||
<div className="flex flex-col items-center text-center space-y-12 pt-20 pb-32">
|
||||
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-cyan-500/10 border border-cyan-500/30 backdrop-blur-sm animate-fade-in">
|
||||
<div className="w-2 h-2 rounded-full bg-cyan-400 animate-pulse" />
|
||||
<span className="text-sm text-cyan-300 font-medium">企业级技能注册中心</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6 max-w-5xl">
|
||||
<h1 className="text-7xl md:text-8xl lg:text-9xl font-black tracking-tight animate-fade-up">
|
||||
<span className="block text-transparent bg-clip-text bg-gradient-to-r from-cyan-300 via-blue-400 to-violet-400">
|
||||
SkillHub
|
||||
</span>
|
||||
</h1>
|
||||
<p className="text-2xl md:text-3xl text-slate-300 font-light leading-relaxed animate-fade-up delay-1">
|
||||
发布、发现、管理
|
||||
<span className="text-cyan-400 font-medium"> Agent 技能包</span>
|
||||
</p>
|
||||
<p className="text-lg text-slate-400 max-w-2xl mx-auto animate-fade-up delay-2">
|
||||
自托管的私有技能注册平台,为团队提供安全、高效的技能共享与协作空间
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="w-full max-w-3xl animate-fade-up delay-3">
|
||||
<div className="relative group">
|
||||
<div className="absolute -inset-1 bg-gradient-to-r from-cyan-500 to-violet-500 rounded-2xl blur opacity-25 group-hover:opacity-40 transition duration-500" />
|
||||
<div className="relative bg-slate-900/80 backdrop-blur-xl rounded-2xl p-2 border border-slate-700/50">
|
||||
<SearchBar onSearch={handleSearch} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center justify-center gap-4 animate-fade-up delay-4">
|
||||
<Button
|
||||
size="lg"
|
||||
className="bg-gradient-to-r from-cyan-500 to-blue-500 hover:from-cyan-400 hover:to-blue-400 text-white font-semibold px-8 py-6 text-lg rounded-xl shadow-lg shadow-cyan-500/25 hover:shadow-cyan-500/40 transition-all duration-300 hover:scale-105"
|
||||
onClick={() => navigate({ to: '/search', search: { q: '', sort: 'relevance', page: 1 } })}
|
||||
>
|
||||
探索技能
|
||||
</Button>
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="border-2 border-slate-600 hover:border-cyan-500 text-slate-200 hover:text-cyan-300 font-semibold px-8 py-6 text-lg rounded-xl backdrop-blur-sm bg-slate-800/30 hover:bg-slate-800/50 transition-all duration-300"
|
||||
onClick={() => navigate({ to: '/dashboard/publish' })}
|
||||
>
|
||||
发布技能
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-8 md:gap-16 pt-12 animate-fade-up delay-5">
|
||||
{Object.entries(stats).map(([key, value]) => (
|
||||
<div key={key} className="text-center group cursor-default">
|
||||
<div className="text-4xl md:text-5xl font-bold text-transparent bg-clip-text bg-gradient-to-br from-cyan-300 to-blue-400 group-hover:from-cyan-200 group-hover:to-blue-300 transition-all duration-300">
|
||||
{value}
|
||||
</div>
|
||||
<div className="text-sm md:text-base text-slate-400 mt-2 capitalize">
|
||||
{key === 'skills' && '技能包'}
|
||||
{key === 'downloads' && '下载量'}
|
||||
{key === 'teams' && '团队'}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-20 space-y-16">
|
||||
<div className="text-center space-y-4 animate-fade-up">
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-slate-100">
|
||||
为什么选择 <span className="text-transparent bg-clip-text bg-gradient-to-r from-cyan-400 to-violet-400">SkillHub</span>
|
||||
</h2>
|
||||
<p className="text-lg text-slate-400 max-w-2xl mx-auto">
|
||||
专为企业打造的技能管理平台,提供完整的发布、发现、治理能力
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{features.map((feature, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="group relative p-6 rounded-2xl bg-slate-800/40 backdrop-blur-sm border border-slate-700/50 hover:border-cyan-500/50 transition-all duration-300 hover:scale-105 animate-fade-up"
|
||||
style={{ animationDelay: (idx * 0.1) + 's' }}
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-cyan-500/5 to-violet-500/5 rounded-2xl opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
||||
<div className="relative space-y-3">
|
||||
<div className="text-4xl">{feature.icon}</div>
|
||||
<h3 className="text-xl font-bold text-slate-100 group-hover:text-cyan-300 transition-colors">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p className="text-slate-400 leading-relaxed">
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-20">
|
||||
<div className="relative max-w-4xl mx-auto p-12 rounded-3xl bg-gradient-to-br from-cyan-500/10 to-violet-500/10 backdrop-blur-sm border border-cyan-500/30 text-center space-y-8 animate-fade-up">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-cyan-500/5 to-violet-500/5 rounded-3xl blur-xl" />
|
||||
<div className="relative space-y-6">
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-slate-100">
|
||||
准备好开始了吗?
|
||||
</h2>
|
||||
<p className="text-lg text-slate-300 max-w-2xl mx-auto">
|
||||
一条命令即可启动本地开发环境,体验完整的技能管理流程
|
||||
</p>
|
||||
<div className="inline-block p-4 rounded-xl bg-slate-900/80 backdrop-blur-sm border border-slate-700/50">
|
||||
<code className="text-cyan-400 text-lg font-mono">make dev-all</code>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center justify-center gap-4 pt-4">
|
||||
<Button
|
||||
size="lg"
|
||||
className="bg-gradient-to-r from-cyan-500 to-blue-500 hover:from-cyan-400 hover:to-blue-400 text-white font-semibold px-8 py-6 text-lg rounded-xl shadow-lg shadow-cyan-500/25 hover:shadow-cyan-500/40 transition-all duration-300 hover:scale-105"
|
||||
onClick={() => navigate({ to: '/search', search: { q: '', sort: 'relevance', page: 1 } })}
|
||||
>
|
||||
立即体验
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 border-t border-slate-800/50 backdrop-blur-sm">
|
||||
<div className="container mx-auto px-6 py-8">
|
||||
<div className="flex flex-col md:flex-row items-center justify-between gap-4 text-slate-400 text-sm">
|
||||
<div>© 2026 SkillHub. MIT License.</div>
|
||||
<div className="flex items-center gap-6">
|
||||
<a href="#" className="hover:text-cyan-400 transition-colors">文档</a>
|
||||
<a href="#" className="hover:text-cyan-400 transition-colors">GitHub</a>
|
||||
<a href="#" className="hover:text-cyan-400 transition-colors">社区</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue